* Ignore folder added by VS codespaces

* Use fantasticon and fix codepoint issues

* Don't generate SVG font

* Handle JSON with numbers

* Generate size-independent font file

* Generate woff and woff2 assets

* Initial groundwork for React font icons

* Create font React icons

* Copy font files to output

* Improve styling

* Create npm workspace and test app for icons

* Add export map to @fluentui/react-icons

* Fix icon-app

* Create and copy codepoint file to be used by webpack plugin

* Working prototype for subsetting webpack plugin

* Update publish script

* Update package-lock.json after previous package.json changes

* Add importer to root workspace

* Update other build definitions

* Use latest Node

* Update documentation

* Add build script to test app

* Fix flutter script

* Fix minor comments from PR

* Rename OneSize to Resizable

Co-authored-by: tomi-msft <66456876+tomi-msft@users.noreply.github.com>
This commit is contained in:
Michael Loughry 2022-05-09 13:43:53 -07:00 коммит произвёл GitHub
Родитель f615657b9a
Коммит 8b56c88328
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
37 изменённых файлов: 17314 добавлений и 8872 удалений

Просмотреть файл

@ -8,13 +8,12 @@ steps:
- task: UseNode@1
inputs:
version: '11.x'
version: '18.x'
- task: Npm@1
displayName: Run npm install
inputs:
command: 'install'
workingDir: 'importer'
- task: Npm@1
displayName: Run generate script

Просмотреть файл

@ -8,13 +8,12 @@ steps:
- task: UseNode@1
inputs:
version: '11.x'
version: '18.x'
- task: Npm@1
displayName: Run npm install
inputs:
command: 'install'
workingDir: 'importer'
- task: Npm@1
displayName: Run generate script

6
.github/workflows/bump-version.yml поставляемый
Просмотреть файл

@ -65,6 +65,12 @@ jobs:
sed -i.bk -r "s/\"version\": \"[0-9]+\.[0-9]+\.[0-9]+\"/\"version\": \"$NEW_VERSION\"/g" packages/react-icons/package.json
rm packages/react-icons/package.json.bk
# Needs to be "-E" instead of "-r" on macOS
- name: Replace version number in react-icons-font-subsetting-webpack-plugin/package.json
run: |
sed -i.bk -r "s/\"version\": \"[0-9]+\.[0-9]+\.[0-9]+\"/\"version\": \"$NEW_VERSION\"/g" packages/react-icons-font-subsetting-webpack-plugin/package.json
rm packages/react-icons-font-subsetting-webpack-plugin/package.json.bk
- name: Config git credentials
run: git config user.email "flubuild@microsoft.com" && git config user.name "Fluent Build System"

37
.github/workflows/pr.yml поставляемый
Просмотреть файл

@ -33,13 +33,12 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Use Node 11
- name: Use Node 18
uses: actions/setup-node@v1
with:
node-version: 11.x
node-version: 18.x
- run: npm install
working-directory: importer
- name: Run generate script
run: npm run deploy:ios
@ -52,13 +51,12 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Use Node 11
- name: Use Node 18
uses: actions/setup-node@v1
with:
node-version: 11.x
node-version: 18.x
- run: npm install
working-directory: importer
- name: Run generate script
run: npm run deploy:android
@ -88,13 +86,12 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Use Node 11
- name: Use Node 18
uses: actions/setup-node@v1
with:
node-version: 11.x
node-version: 18.x
- run: npm install
working-directory: importer
- name: Run generate script
run: npm run deploy:flutter
@ -119,16 +116,12 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Use Node 11
- name: Use Node 18
uses: actions/setup-node@v1
with:
node-version: 11.x
node-version: 18.x
- run: npm install
working-directory: importer
- run: npm install
working-directory: packages/svg-icons
- name: Run build
run: npm run build
@ -141,15 +134,21 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Use Node 11
- name: Use Node 18
uses: actions/setup-node@v1
with:
node-version: 11.x
node-version: 18.x
- run: npm install
working-directory: importer
- run: |
npm install
npm run build
working-directory: packages/react-icons
- run: |
npm run build
working-directory: packages/react-icons-font-subsetting-webpack-plugin
- run: |
npm run test
working-directory: packages/react-icons-font-subsetting-webpack-plugin

27
.github/workflows/publish.yml поставляемый
Просмотреть файл

@ -43,10 +43,10 @@ jobs:
os.system(f'echo \"REACT_VERSION={major}.{minor}.{int(asset) + 1}-rc.{int(rc) + 1}\" >> \$GITHUB_ENV')
"""
- name: Use Node 11
- name: Use Node 18
uses: actions/setup-node@v1
with:
node-version: 11.x
node-version: 18.x
- run: npm install
working-directory: importer
@ -113,18 +113,30 @@ jobs:
sed -i.bk -r "s/\"version\": \"[0-9]+\.[0-9]+\.[0-9]+(-beta\.[0-9]+)?(-rc\.[0-9]+)?\"/\"version\": \"$REACT_VERSION\"/g" packages/react-icons/package.json
rm packages/react-icons/package.json.bk
# Needs to be "-E" instead of "-r" on macOS
- name: Replace version number in react-icons-font-subsetting-webpack-plugin/package.json
run: |
sed -i.bk -r "s/\"version\": \"[0-9]+\.[0-9]+\.[0-9]+(-beta\.[0-9]+)?(-rc\.[0-9]+)?\"/\"version\": \"$REACT_VERSION\"/g" packages/react-icons-font-subsetting-webpack-plugin/package.json
rm packages/react-icons-font-subsetting-webpack-plugin/package.json.bk
- name: Install dependencies
run: npm install
- name: Build SVG library
run: |
npm install
npm run build
working-directory: packages/svg-icons
- name: Build React library
run: |
npm install
npm run build
working-directory: packages/react-icons
- name: Build Webpack plugin library
run: |
npm run build
working-directory: packages/react-icons-font-subsetting-webpack-plugin
- uses: JS-DevTools/npm-publish@v1
with:
token: ${{ secrets.NPM_TOKEN }}
@ -138,6 +150,13 @@ jobs:
package: packages/react-icons/package.json
tag: rc
- uses: JS-DevTools/npm-publish@v1
with:
token: ${{ secrets.NPM_TOKEN }}
access: public
package: packages/react-icons-font-subsetting-webpack-plugin/package.json
tag: rc
## Android
- name: Run Android generate script
env:

4
.gitignore поставляемый
Просмотреть файл

@ -30,4 +30,6 @@ packages/react-icons/yarn.lock
packages/react-icons/package-lock.json
# Some Fluent tooling generates this for internal tracking assistance. Make sure it doesn't get committed.
SvgList.txt
SvgList.txt
.venv

107
importer/generateFont.js Normal file
Просмотреть файл

@ -0,0 +1,107 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
// @ts-check
const fs = require("fs/promises");
const mkdirp = require('mkdirp');
const path = require("path");
const { promisify } = require('util');
const glob = promisify(require('glob'));
const process = require("process");
const argv = require("yargs").boolean("selector").default("selector", false).argv;
const _ = require("lodash");
const fantasticon = require('fantasticon');
const SRC_PATH = argv.source;
const DEST_PATH = argv.dest;
const ICON_TYPE = argv.iconType;
const CODEPOINTS_FILE = argv.codepoints;
if (!SRC_PATH) {
throw new Error("SVG source folder not specified by --source");
}
if (!DEST_PATH) {
throw new Error("Output destination folder not specified by --dest");
}
if (!(ICON_TYPE === 'Filled' || ICON_TYPE === 'Regular' || ICON_TYPE === 'Resizable')) {
throw new Error("Icon type not specified");
}
async function main() {
await mkdirp(DEST_PATH);
const stagingFolder = path.resolve(DEST_PATH, ICON_TYPE);
await mkdirp(stagingFolder);
const svgFiles = await glob(path.resolve(SRC_PATH, `*_${ICON_TYPE === 'Resizable' ? '20_{filled,regular}' : ICON_TYPE.toLowerCase()}.svg`));
const icons = new Set(svgFiles.map(file => path.basename(file).replace(/\.svg$/, '')));
if (icons.size > 6400) {
throw new Error('Too many icons to fit into the Unicode private use area (0xE000-0xF8FF). See https://unicode-table.com/en/blocks/private-use-area/')
}
// Copy all icons of the given icon type to the staging folder
await Promise.all((svgFiles).map(
async svgFile => fs.copyFile(svgFile, path.resolve(stagingFolder, path.basename(svgFile)))
));
// Generate the font and associated assets
await fantasticon.generateFonts({
inputDir: stagingFolder,
outputDir: path.resolve(DEST_PATH),
name: `FluentSystemIcons-${ICON_TYPE}`,
fontTypes: [fantasticon.ASSET_TYPES.WOFF2, fantasticon.ASSET_TYPES.WOFF, fantasticon.ASSET_TYPES.TTF],
assetTypes: [fantasticon.ASSET_TYPES.CSS, fantasticon.ASSET_TYPES.HTML, fantasticon.ASSET_TYPES.JSON],
formatOptions: { json: { indent: 2 } },
codepoints: await getCodepoints(icons),
fontHeight: 500,
normalize: true
});
// Clean up staging folder
await Promise.all(svgFiles.map(
async svgFile => fs.unlink(path.resolve(stagingFolder, path.basename(svgFile)))
));
if ((await fs.readdir(stagingFolder)).length === 0) {
await fs.rmdir(stagingFolder);
}
}
/**
*
* @param {Set<string>} icons - Set of icons being consumed into the font
* @returns {Promise<Record<string, number>>}
*/
async function getCodepoints(icons) {
/** @type {Record<string, number>} */
let codepoints = {};
if (CODEPOINTS_FILE) {
const originalCodepoints = JSON.parse(await fs.readFile(CODEPOINTS_FILE, 'utf8'));
codepoints = Object.fromEntries(
Object.entries(originalCodepoints)
.filter(([iconName]) => icons.has(iconName))
.map(([iconName, codepoint]) => [iconName, typeof codepoint === 'number' ? codepoint : Number.parseInt(codepoint)])
);
}
// Fix any codepoints outside the private use area
let nextCodePoint = 0xe000;
let usedCodePoints = new Set(Object.values(codepoints));
for (const iconName of icons) {
const originalCodepoint = codepoints[iconName]
if (!originalCodepoint || originalCodepoint < 0xe000 || originalCodepoint > 0xf8ff) {
// Find a new free codepoint
while (usedCodePoints.has(nextCodePoint)) {
nextCodePoint++;
}
usedCodePoints.add(nextCodePoint);
codepoints[iconName] = nextCodePoint;
}
}
return codepoints;
}
main();

Просмотреть файл

@ -88,7 +88,7 @@ function writeCodeForJson(srcPath, iconClassFile, rtlIcons) {
let style = match[3];
FILE_NAME_REGEX.lastIndex = 0;
let codepoint = orderedJsonData[fullName].replace("\\", "0x");
let codepoint = orderedJsonData[fullName];
let identifier = `${name}_${size}_${style}`;
let matchTextDirection = rtlIcons.includes(fullName) ? `, matchTextDirection: true` : "";

2086
importer/package-lock.json сгенерированный

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -19,9 +19,10 @@
"deploy:android": "npm run build:android && rm -rf ../android/library/src/main/res/drawable && mkdir ../android/library/src/main/res/drawable && find ./dist/ -type f -name \"*.xml\" -maxdepth 1 -exec cp {} ../android/library/src/main/res/drawable \\; && npm run clean",
"deploy:react": "npm run build:react && rm -rf ../react/src/components && mkdir ../react/src/components && find ./dist/ -type f -name \"*.tsx\" -exec cp {} ../react/src/components \\; && npm run clean",
"deploy:ios": "npm run build:ios && python3 process_ios_assets.py && npm run clean",
"generate:font-regular": "icon-font-generator dist/*_regular.svg -o dist/fonts --name FluentSystemIcons-Regular --types ttf --codepoints ../fonts/FluentSystemIcons-Regular.json --height 500",
"generate:font-filled": "icon-font-generator dist/*_filled.svg -o dist/fonts --name FluentSystemIcons-Filled --types ttf --codepoints ../fonts/FluentSystemIcons-Filled.json --height 500",
"build:fonts": "npm run generate:svg && find ./dist -type f -name '*.svg' -exec svgo --config svgo_config.yml {} + && mkdir dist/fonts && npm run generate:font-regular && npm run generate:font-filled && replace '\\\\\\\\' '0x' dist/fonts/*.json",
"generate:font-regular": "node generateFont.js --source=dist --dest=dist/fonts --iconType=Regular --codepoints=../fonts/FluentSystemIcons-Regular.json",
"generate:font-filled": "node generateFont.js --source=dist --dest=dist/fonts --iconType=Filled --codepoints=../fonts/FluentSystemIcons-Filled.json",
"generate:font-resizable": "node generateFont.js --source=dist --dest=dist/fonts --iconType=Resizable",
"build:fonts": "npm run generate:svg && find ./dist -type f -name '*.svg' -exec svgo --config svgo_config.yml {} + && mkdir dist/fonts && npm run generate:font-regular && npm run generate:font-filled && npm run generate:font-resizable && replace '\\\\\\\\' '0x' dist/fonts/*.json",
"deploy:fonts": "npm run build:fonts && cp -a dist/fonts/* ../fonts && npm run clean",
"generate:flutter-icon-lib-class": "node generate_flutter_lib_class.js --source=../fonts/FluentSystemIcons-Regular.json ../fonts/FluentSystemIcons-Filled.json --dest=dist/flutter",
"generate:flutter-icon-demo-class": "node generate_flutter_demo_class.js --source=../fonts/FluentSystemIcons-Regular.json ../fonts/FluentSystemIcons-Filled.json --dest=dist/flutter",
@ -32,8 +33,10 @@
"license": "Microsoft",
"devDependencies": {
"avocado": "1.0.0",
"icon-font-generator": "^2.1.10",
"fantasticon": "^1.2.3",
"glob": "^8.0.1",
"lodash": "4.17.21",
"mkdirp": "^1.0.4",
"react": "~17.0.1",
"replace": "^1.2.0",
"shx": "^0.3.2",

16154
package-lock.json сгенерированный Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

15
package.json Normal file
Просмотреть файл

@ -0,0 +1,15 @@
{
"name": "fluent-system-icons",
"private": true,
"workspaces": [
"packages/*",
"importer"
],
"description": "![CI](https://github.com/microsoft/fluentui-system-icons/workflows/CI/badge.svg)",
"version": "1.0.0",
"main": "index.js",
"repository": {
"type": "git",
"url": "git+https://github.com/microsoft/fluentui-system-icons.git"
}
}

Просмотреть файл

@ -0,0 +1,27 @@
{
"name": "icon-app",
"version": "1.0.0",
"description": "Test/demo app for @fluentui/react-icons",
"main": "index.js",
"private": true,
"scripts": {
"build": "webpack"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@types/react": "^17.0.2",
"@types/react-dom": "^17.0.0",
"html-webpack-plugin": "^5.5.0",
"ts-loader": "^9.2.9",
"typescript": "^4.6.3",
"webpack": "^5.72.0",
"webpack-cli": "^4.9.2"
},
"dependencies": {
"@fluentui/react-icons": "*",
"@griffel/react": "^1.0.3",
"react": "^17.0.1",
"react-dom": "^17.0.1"
}
}

Просмотреть файл

@ -0,0 +1,108 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import * as fontsModule from '@fluentui/react-icons/lib/fonts';
// @ts-ignore
import * as svgModule from '@fluentui/react-icons/lib/svg';
import { makeStyles } from "@griffel/react";
const fontComponents = filterModuleImports(fontsModule);
const svgComponents = filterModuleImports(svgModule);
function main() {
const rootDiv = document.createElement('div');
document.body.append(rootDiv);
ReactDOM.render(<MainComponent />, rootDiv);
}
const useRootStyles = makeStyles({
root: {
display: 'flex',
flexDirection: 'row',
flexWrap: 'wrap',
}
});
function MainComponent() {
const styles = useRootStyles();
return (<div className={styles.root}>
{Object.keys(fontComponents).map(name => <IconCell FontIcon={fontComponents[name]} SvgIcon={svgComponents[name]} name={name} key={name} />)}
</div>)
}
const useCellStyles = makeStyles({
root: {
display: 'flex',
flexDirection: 'column',
justifyItems: 'end',
height: '120px',
width: '120px',
backgroundColor: "#ccc",
marginLeft: '5px',
marginRight: '5px',
marginTop: '5px',
marginBottom: '5px'
},
iconZone: {
display: 'flex',
flexDirection: 'row',
justifyItems: 'center'
},
iconCell: {
backgroundColor: '#fff',
flexBasis: '50%',
flexShrink: '1',
flexGrow: '1',
display: 'flex',
flexDirection: 'column',
justifyItems: 'end',
fontSize: '40px',
marginLeft: '5px',
marginRight: '5px',
marginTop: '5px',
marginBottom: '5px'
},
sublabel: {
display: 'inline-block',
fontSize: '14px',
height: '20px'
},
mainLabel: {
fontSize: '14px',
overflowX: 'hidden',
textOverflow: 'ellipsis',
marginLeft: '5px',
marginRight: '5px',
marginTop: '5px',
marginBottom: '5px'
}
});
function IconCell({ FontIcon, SvgIcon, name }: { FontIcon: React.ComponentType, SvgIcon: React.ComponentType, name: string }) {
const styles = useCellStyles()
return <div className={styles.root}>
<div className={styles.iconZone}>
<div className={styles.iconCell}><FontIcon /><span className={styles.sublabel}>font</span></div>
<div className={styles.iconCell}><SvgIcon /><span className={styles.sublabel}>svg</span></div>
</div>
<span className={styles.mainLabel} title={name}>{name}</span>
</div>
}
function filterModuleImports(mod: Record<string, unknown>): Record<string, React.ComponentType> {
const importsToFilter = new Set(['wrapIcon', 'bundleIcon', 'useIconState', 'iconFilledClassName', 'iconRegularClassName', '_esModule']);
const components: Record<string, React.ComponentType> = {};
for (const [name, possibleComponent] of Object.entries(mod)) {
if (!importsToFilter.has(name)) {
components[name] = possibleComponent as React.ComponentType;
}
}
return components;
}
main();

Просмотреть файл

@ -0,0 +1,101 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Projects */
// "incremental": true, /* Enable incremental compilation */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "es2020", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
"jsx": "react", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
// "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
/* Modules */
"module": "esnext", /* Specify what module code is generated. */
// "rootDir": "./src", /* Specify the root folder within your source files. */
"moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
"rootDirs": ["./src", "../react-icons/src"], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "resolveJsonModule": true, /* Enable importing .json files */
// "noResolve": true, /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
/* Emit */
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
"outDir": "./lib", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
/* Type Checking */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
// "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
// "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}

Просмотреть файл

@ -0,0 +1,21 @@
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
resolve: {
// Add `.ts` and `.tsx` as a resolvable extension.
extensions: [".ts", ".tsx", ".js"]
},
module: {
rules: [
// all files with a `.ts` or `.tsx` extension will be handled by `ts-loader`
{ test: /\.tsx?$/, loader: "ts-loader" },
{
test: /\.(ttf|woff2?)$/,
type: 'asset',
}
]
},
plugins: [
new HtmlWebpackPlugin()
]
};

Просмотреть файл

@ -0,0 +1 @@
lib/

Просмотреть файл

@ -0,0 +1,33 @@
@fluentui/react-icons-font-subsetting-webpack-plugin
===
This package includes a plugin for `webpack@>=5.0.0` to subset the icon font files used by `@fluentui/react-icons` when using the `"fluentIcontFont"` condition in `resolve.conditionNames`.
If `optimization.usedExports` is enabled (as it is by default), this plugin will subset the font files to only include the glyphs actually used by your build.
Usage
---
```js
// webpack.config.js
const {default: FluentUIReactIconsFontSubsettingPlugin} = require('@fluentui/react-icons-font-subsetting-webpack-plugin');
module.exports = {
module: {
rules: [
// Treat the font files as webpack assets
{
test: /\.(ttf|woff2?)$/,
type: 'asset',
}
]
},
resolve: {
// Include 'fluentIcontFont' to use the font implementation of the Fluent icons
conditionNames: ['fluentIcontFont', 'import']
},
plugins: [
// Include this plugin
new FluentUIReactIconsFontSubsettingPlugin(),
],
};
```

Просмотреть файл

@ -0,0 +1,38 @@
{
"name": "@fluentui/react-icons-font-subsetting-webpack-plugin",
"version": "1.0.0",
"description": "Webpack plugin to subset the icon fonts used by @fluentui/react-icons based on which icons are used.",
"main": "lib/index.js",
"scripts": {
"test": "webpack -c test/webpack.config.js",
"build": "tsc"
},
"engines": {
"node": ">=14.5.0"
},
"repository": {
"type": "git",
"url": "git+https://github.com/microsoft/fluentui-system-icons.git",
"directory": "packages/react-icons-font-subsetting-webpack-plugin"
},
"author": "Michael Loughry <miclo@microsoft.com>",
"license": "MIT",
"bugs": {
"url": "https://github.com/microsoft/fluentui-system-icons/issues"
},
"homepage": "https://github.com/microsoft/fluentui-system-icons/packages/react-icons-font-subsetting-webpack-plugin#readme",
"devDependencies": {
"typescript": "^4.6.3",
"webpack": "^5.72.0",
"@fluentui/react-icons": "*"
},
"dependencies": {
"subset-font": "^1.4.0"
},
"peerDependencies": {
"webpack": ">=5.0.0"
},
"files": [
"lib/*"
]
}

Просмотреть файл

@ -0,0 +1,132 @@
import * as webpack from "webpack";
import subsetFont from "subset-font";
import { extname, dirname, resolve } from "path";
import { readFile } from 'fs/promises';
const PLUGIN_NAME = "FluentUIReactIconsFontSubsettingPlugin";
const FONT_FILES_BASE_NAMES = [
"FluentSystemIcons-Filled",
"FluentSystemIcons-Resizable",
"FluentSystemIcons-Regular",
];
const FONT_EXTENSIONS = [
'.ttf',
'.woff',
'.woff2'
]
export default class FluentUIReactIconsFontSubsettingPlugin
implements webpack.WebpackPluginInstance {
apply(compiler: webpack.Compiler) {
compiler.hooks.compilation.tap(
PLUGIN_NAME,
(compilation: webpack.Compilation) => {
compilation.hooks.optimizeAssets.tapPromise(PLUGIN_NAME, async () => {
// There could be multiple instances of `@fluentui/react-icons`, and they need to be subset separately
const packageToUsedFontExports: Map<string, Set<string>> = new Map<string, Set<string>>();
for (const m of compilation.modules) {
if (isFluentUIReactFontChunk(m)) {
const usedModuleExports = compilation.moduleGraph.getUsedExports(m, undefined);
if (usedModuleExports === null || typeof usedModuleExports === 'boolean') {
// Either all exports are used or there's no info on used exports
continue;
}
const pkgLibPath = resolve(dirname(m.resource), '../..');
const usedPkgExports = packageToUsedFontExports.get(pkgLibPath) ?? new Set<string>();
for (const icon of usedModuleExports) {
usedPkgExports.add(icon);
}
packageToUsedFontExports.set(pkgLibPath, usedPkgExports);
}
}
const optimizationPromises: Promise<void>[] = [];
for (const [pkgLibPath, usedExports] of packageToUsedFontExports) {
for (const { assetName, codepoints: codepointMap } of await getFontAssetsAndCodepoints(pkgLibPath, compilation)) {
optimizationPromises.push(optimizeFontAsset(codepointMap, usedExports, compilation, assetName));
}
}
await optimizationPromises;
});
}
);
}
}
async function optimizeFontAsset(codepointMap: Record<string, number>, usedExports: Set<string>, compilation: webpack.Compilation, assetName: string) {
const subsetText = Object.entries(codepointMap)
.filter(([glyphName]) => usedExports.has(glyphName))
.map(([, codepoint]) => String.fromCodePoint(codepoint))
.join('');
let source = compilation.assets[assetName].source();
if (typeof source === "string") {
source = Buffer.from(source);
}
compilation.assets[assetName] = new webpack.sources.RawSource(
await subsetFont(
source,
subsetText,
{
targetFormat: getTargetFormat(assetName),
}
)
);
}
function getTargetFormat(assetName: string) {
switch (extname(assetName)) {
case ".woff":
return "woff";
case ".woff2":
return "woff2";
default:
return "sfnt";
}
}
function isNormalModule(m: webpack.Module): m is webpack.NormalModule {
return m instanceof webpack.NormalModule;
}
function isFluentUIReactFontChunk(m: webpack.Module): m is webpack.NormalModule {
if (isNormalModule(m)) {
return /react-icons[\/\\]lib(-cjs)?[\/\\]fonts[\/\\](sizedIcons|icons)[\/\\]chunk-\d+\.js$/.test(m.resource)
}
return false;
}
async function getFontAssetsAndCodepoints(pkgLibPath: string, compilation: webpack.Compilation): Promise<{ assetName: string, codepoints: Record<string, number> }[]> {
const utilsFontsFolder = resolve(pkgLibPath, 'utils/fonts');
const codepoints: Record<string, Record<string, number>> = Object.fromEntries(await Promise.all(
FONT_FILES_BASE_NAMES.map(async fontBaseName => [fontBaseName, JSON.parse(await readFile(resolve(utilsFontsFolder, `${fontBaseName}.json`), 'utf8'))])
))
const fontPaths = new Map<string, Record<string, number>>(
FONT_FILES_BASE_NAMES.flatMap(fontBaseName => FONT_EXTENSIONS.map(ext => [resolve(utilsFontsFolder, `${fontBaseName}${ext}`), codepoints[fontBaseName]])
));
const result: { assetName: string, codepoints: Record<string, number> }[] = [];
for (const m of compilation.modules) {
if (isNormalModule(m) && fontPaths.has(m.resource)) {
const assetName = m.buildInfo?.filename;
if (assetName) {
result.push({ assetName, codepoints: fontPaths.get(m.resource)! })
}
}
}
return result;
}

Просмотреть файл

@ -0,0 +1,13 @@
// Typings for the `subset-font` npm package
declare module "subset-font" {
function subsetFont(
buffer: Buffer,
text: string,
options?: {
targetFormat?: "sfnt" | "woff" | "woff2";
preserveNameIds?: boolean;
}
): Promise<Buffer>;
export default subsetFont;
}

Просмотреть файл

@ -0,0 +1,7 @@
import {XboxConsole24Filled, BoardGames20Regular, GamesFilled} from '@fluentui/react-icons';
console.dir({
XboxConsole24Filled,
BoardGames20Regular,
GamesFilled
})

Просмотреть файл

@ -0,0 +1,41 @@
const {default: FluentUIReactIconsFontSubsettingPlugin} = require('../lib/');
const {resolve} = require('path');
const FONT_THRESHOLD_SIZE = 2 * 1_024; // 2kb
module.exports = {
context: __dirname,
mode: 'production',
module: {
rules: [
{
test: /\.(ttf|woff2?)$/,
type: 'asset',
generator: {
filename: `[name]-[contenthash][ext]`,
dataUrl: {},
},
}
]
},
output: {
path: resolve(__dirname, 'dist'),
},
resolve: {
conditionNames: ['fluentIcontFont', 'import']
},
plugins: [
new FluentUIReactIconsFontSubsettingPlugin(),
{
apply(compiler) {
compiler.hooks.afterEmit.tap('test-subsetting', (compilation) => {
for (const [assetName, source] of Object.entries(compilation.assets)) {
if (/\.(ttf|woff2?)$/.test(assetName) && source.size() > FONT_THRESHOLD_SIZE) {
throw new Error(`Asset "${assetName}" does not appear to have been properly subset.`)
}
}
})
}
}
],
};

Просмотреть файл

@ -0,0 +1,101 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Projects */
// "incremental": true, /* Enable incremental compilation */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "es2020", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
// "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
/* Modules */
"module": "commonjs", /* Specify what module code is generated. */
"rootDir": "./src", /* Specify the root folder within your source files. */
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "resolveJsonModule": true, /* Enable importing .json files */
// "noResolve": true, /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
/* Emit */
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
"outDir": "./lib", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
/* Type Checking */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
// "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
// "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}

Просмотреть файл

@ -86,6 +86,22 @@ ReactDOM.render(
)
```
Using the icon font
---
If, for performance or other reasons, you wish to use the font implementation of these icons rather than the SVG implementation, you can specify `"fluentIcontFont"` as a condition for the import, either via [Node >= 12.19.0](https://nodejs.org/dist/latest-v16.x/docs/api/packages.html#resolving-user-conditions) or [webpack >= 5.0.0](https://webpack.js.org/configuration/resolve/#resolveconditionnames).
```js
// webpack.config.js
module.exports = {
//...
resolve: {
conditionNames: ['fluentIcontFont', 'require', 'node'],
},
};
```
If you do choose this route, you may wish to use `@fluentui/react-icons-font-subsetting-webpack-plugin` to optimize the font assets.
Viewing the icons in a webpage
---
You can view the full list of available icons by type: [regular](https://github.com/microsoft/fluentui-system-icons/blob/master/icons_regular.md) or [filled](https://github.com/microsoft/fluentui-system-icons/blob/master/icons_filled.md)

175
packages/react-icons/convert-font.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,175 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
// @ts-check
const fs = require("fs/promises");
const path = require("path");
const process = require("process");
const argv = require("yargs").boolean("selector").default("selector", false).argv;
const _ = require("lodash");
const mkdirp = require('mkdirp');
const { promisify } = require('util');
const glob = promisify(require('glob'));
// @ts-ignore
const SRC_PATH = argv.source;
// @ts-ignore
const DEST_PATH = argv.dest;
// @ts-ignore
const CODEPOINT_DEST_PATH = argv.codepointDest;
if (!SRC_PATH) {
throw new Error("Icon source folder not specified by --source");
}
if (!DEST_PATH) {
throw new Error("Output destination folder not specified by --dest");
}
if (!CODEPOINT_DEST_PATH) {
throw new Error("Output destination folder for codepoint map not specified by --dest");
}
processFiles(SRC_PATH, DEST_PATH)
async function processFiles(src, dest) {
/** @type string[] */
const indexContents = [];
// make file for resizeable icons
const iconPath = path.join(dest, 'icons')
const iconContents = await processFolder(src, CODEPOINT_DEST_PATH, true);
await cleanFolder(iconPath);
await Promise.all(iconContents.map(async (chunk, i) => {
const chunkFileName = `chunk-${i}`
const chunkPath = path.resolve(iconPath, `${chunkFileName}.tsx`);
indexContents.push(`export * from './icons/${chunkFileName}'`);
await fs.writeFile(chunkPath, chunk);
}));
// make file for sized icons
const sizedIconPath = path.join(dest, 'sizedIcons');
const sizedIconContents = await processFolder(src, CODEPOINT_DEST_PATH, false)
await cleanFolder(sizedIconPath);
await Promise.all(sizedIconContents.map(async (chunk, i) => {
const chunkFileName = `chunk-${i}`
const chunkPath = path.resolve(sizedIconPath, `${chunkFileName}.tsx`);
indexContents.push(`export * from './sizedIcons/${chunkFileName}'`);
await fs.writeFile(chunkPath, chunk);
}));
const indexPath = path.join(dest, 'index.tsx')
// Finally add the interface definition and then write out the index.
indexContents.push('export { FluentIconsProps } from \'../utils/FluentIconsProps.types\'');
indexContents.push('export { default as wrapIcon } from \'../utils/wrapIcon\'');
indexContents.push('export { default as bundleIcon } from \'../utils/bundleIcon\'');
indexContents.push('export * from \'../utils/useIconState\'');
indexContents.push('export * from \'../utils/constants\'');
await fs.writeFile(indexPath, indexContents.join('\n'));
}
/**
* Process a folder of svg files and convert them to React components, following naming patterns for the FluentUI System Icons
* @param {string} srcPath
* @param {string} codepointMapDestFolder
* @param {boolean} resizable
* @returns { Promise<string[]> } - chunked icon files to insert
*/
async function processFolder(srcPath, codepointMapDestFolder, resizable) {
var files = await glob(resizable ? 'FluentSystemIcons-Resizable.json' : 'FluentSystemIcons-{Filled,Regular}.json', { cwd: srcPath, absolute: true });
/** @type string[] */
const iconExports = [];
await Promise.all(files.map(async (srcFile, index) => {
/** @type {Record<string, number>} */
const iconEntries = JSON.parse(await fs.readFile(srcFile, 'utf8'));
iconExports.push(...generateReactIconEntries(iconEntries, resizable));
return generateCodepointMapForWebpackPlugin(
path.resolve(codepointMapDestFolder, path.basename(srcFile)),
iconEntries,
resizable
);
}));
// chunk all icons into separate files to keep build reasonably fast
/** @type string[][] */
const iconChunks = [];
while (iconExports.length > 0) {
iconChunks.push(iconExports.splice(0, 500));
}
for (const chunk of iconChunks) {
chunk.unshift(`import {createFluentFontIcon} from "../../utils/fonts/createFluentFontIcon";`)
}
/** @type string[] */
const chunkContent = iconChunks.map(chunk => chunk.join('\n'));
return chunkContent;
}
/**
*
* @param {string} destPath
* @param {Record<string,number>} iconEntries
* @param {boolean} resizable
*/
async function generateCodepointMapForWebpackPlugin(destPath, iconEntries, resizable) {
const finalCodepointMap = Object.fromEntries(
Object.entries(iconEntries)
.map(([name, codepoint]) => [getReactIconNameFromGlyphName(name, resizable), codepoint])
);
await fs.writeFile(destPath, JSON.stringify(finalCodepointMap, null, 2));
}
/**
*
* @param {Record<string, number>} iconEntries
* @param {boolean} resizable
* @returns {string[]}
*/
function generateReactIconEntries(iconEntries, resizable) {
/** @type {string[]} */
const iconExports = [];
for (const [iconName, codepoint] of Object.entries(iconEntries)) {
let destFilename = getReactIconNameFromGlyphName(iconName, resizable);
var jsCode = `export const ${destFilename} = /*#__PURE__*/createFluentFontIcon(${JSON.stringify(destFilename)
}, ${JSON.stringify(String.fromCodePoint(codepoint))
}, ${resizable ? 2 /* Resizable */ : /filled$/i.test(iconName) ? 0 /* Filled */ : 1 /* Regular */
}${resizable ? '' : `, ${/(?<=_)\d+(?=_filled|_regular)/.exec(iconName)[0]}`
});`;
iconExports.push(jsCode);
}
return iconExports;
}
/**
*
* @param {string} iconName
* @param {boolean} resizable
* @returns {string}
*/
function getReactIconNameFromGlyphName(iconName, resizable) {
let destFilename = iconName.replace("ic_fluent_", ""); // strip ic_fluent_
destFilename = resizable ? destFilename.replace("20", "") : destFilename;
destFilename = _.camelCase(destFilename); // We want them to be camelCase, so access_time would become accessTime here
destFilename = destFilename.replace(destFilename.substring(0, 1), destFilename.substring(0, 1).toUpperCase()); // capitalize the first letter
return destFilename;
}
async function cleanFolder(folder) {
try {
await fs.access(folder);
await fs.rm(folder, { recursive: true, force: true });
} catch { }
await mkdirp(folder);
}

10
packages/react-icons/convert.js поставляемый
Просмотреть файл

@ -82,10 +82,10 @@ function processFiles(src, dest) {
/**
* Process a folder of svg files and convert them to React components, following naming patterns for the FluentUI System Icons
* @param {string} srcPath
* @param {boolean} oneSize
* @param {boolean} resizable
* @returns { string [] } - chunked icon files to insert
*/
function processFolder(srcPath, destPath, oneSize) {
function processFolder(srcPath, destPath, resizable) {
var files = fs.readdirSync(srcPath)
// These options will be passed to svgr/core
@ -120,18 +120,18 @@ function processFolder(srcPath, destPath, oneSize) {
// }
// indexContents += processFolder(srcFile, joinedDestPath)
} else {
if(oneSize && !file.includes("20")) {
if(resizable && !file.includes("20")) {
return
}
var iconName = file.substr(0, file.length - 4) // strip '.svg'
iconName = iconName.replace("ic_fluent_", "") // strip ic_fluent_
iconName = oneSize ? iconName.replace("20", "") : iconName
iconName = resizable ? iconName.replace("20", "") : iconName
var destFilename = _.camelCase(iconName) // We want them to be camelCase, so access_time would become accessTime here
destFilename = destFilename.replace(destFilename.substring(0, 1), destFilename.substring(0, 1).toUpperCase()) // capitalize the first letter
var iconContent = fs.readFileSync(srcFile, { encoding: "utf8" })
var jsxCode = oneSize ? svgr.default.sync(iconContent, svgrOpts, { filePath: file }) : svgr.default.sync(iconContent, svgrOptsSizedIcons, { filePath: file })
var jsxCode = resizable ? svgr.default.sync(iconContent, svgrOpts, { filePath: file }) : svgr.default.sync(iconContent, svgrOptsSizedIcons, { filePath: file })
var jsCode =
`
const ${destFilename}Icon = (iconProps: FluentIconsProps) => {

5463
packages/react-icons/package-lock.json сгенерированный

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -15,23 +15,32 @@
"clean": "find ./src -type f ! -name \"wrapIcon.tsx\" -name \"*.tsx\" -delete",
"cleanSvg": "rm -rf ./intermediate",
"copy": "node ../../importer/generate.js --source=../../assets --dest=./intermediate --extension=svg --target=react",
"convert": "node convert.js --source=./intermediate --dest=./src",
"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/.",
"convert:svg": "node convert.js --source=./intermediate --dest=./src",
"convert:fonts": "node convert-font.js --source=./src/utils/fonts --dest=./src/fonts --codepointDest=./src/utils/fonts",
"generate:font-regular": "node ../../importer/generateFont.js --source=intermediate --dest=src/utils/fonts --iconType=Regular --codepoints=../../fonts/FluentSystemIcons-Regular.json",
"generate:font-filled": "node ../../importer/generateFont.js --source=intermediate --dest=src/utils/fonts --iconType=Filled --codepoints=../../fonts/FluentSystemIcons-Filled.json",
"generate:font-resizable": "node ../../importer/generateFont.js --source=intermediate --dest=src/utils/fonts --iconType=Resizable",
"generate:font": "npm run generate:font-regular && npm run generate:font-filled && npm run generate:font-resizable",
"rollup": "node ./generateRollup.js",
"optimize": "svgo --folder=./intermediate --precision=2 --disable=removeViewBox,mergePaths",
"unfill": "find ./intermediate -type f -name \"*.svg\" -exec sed -i.bak 's/fill=\"none\"//g' {} \\; && find ./intermediate -type f -name \"*.bak\" -delete",
"build": "npm run copy && npm run optimize && npm run unfill && npm run convert && npm run cleanSvg && npm run build:esm && npm run build:cjs",
"build": "npm run copy && npm run generate:font && npm run optimize && npm run unfill && npm run convert:svg && npm run convert:fonts && npm run cleanSvg && npm run build:esm && npm run build:cjs && npm run copy:font-files",
"build:cjs": "tsc --module commonjs --outDir lib-cjs && babel lib-cjs --out-dir lib-cjs",
"build:esm": "tsc && babel lib --out-dir lib"
},
"devDependencies": {
"@babel/cli": "^7.16.0",
"@babel/core": "^7.16.0",
"@babel/preset-typescript": "^7.16.7",
"@griffel/babel-preset": "^1.0.0",
"@griffel/react": "^1.0.0",
"@svgr/core": "^5.5.0",
"@types/react": "^17.0.2",
"glob": "^7.1.7",
"cpy-cli": "^4.1.0",
"glob": "^7.2.0",
"lodash": "^4.17.21",
"mkdirp": "^1.0.4",
"react": "^17.0.1",
"renamer": "^2.0.1",
"svgo": "^1.3.2",
@ -46,5 +55,29 @@
"files": [
"lib/",
"lib-cjs/"
]
}
],
"exports": {
".": {
"fluentIcontFont": {
"types": "./lib/fonts/index.d.ts",
"import": "./lib/fonts/index.js",
"require": "./lib-cjs/fonts/index.js"
},
"default": {
"types": "./lib/index.d.ts",
"import": "./lib/index.js",
"require": "./lib-cjs/index.js"
}
},
"./lib/fonts": {
"types": "./lib/fonts/index.d.ts",
"import": "./lib/fonts/index.js",
"require": "./lib-cjs/fonts/index.js"
},
"./lib/svg": {
"types": "./lib/index.d.ts",
"import": "./lib/index.js",
"require": "./lib-cjs/index.js"
}
}
}

1
packages/react-icons/src/.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1 @@
fonts/

Просмотреть файл

@ -1,6 +1,6 @@
import * as React from "react";
export interface FluentIconsProps extends React.SVGAttributes<SVGElement> {
export type FluentIconsProps<TBaseAttributes extends (React.SVGAttributes<SVGElement> | React.HTMLAttributes<HTMLElement>) = React.SVGAttributes<SVGElement>> = (TBaseAttributes) & {
primaryFill?: string
className?: string
filled?: boolean

1
packages/react-icons/src/utils/fonts/.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1 @@
FluentSystemIcons*

Просмотреть файл

@ -0,0 +1,99 @@
import * as React from 'react';
import { FluentIconsProps } from '../FluentIconsProps.types';
import { makeStyles, makeStaticStyles, mergeClasses } from "@griffel/react";
import { useIconState } from '../useIconState';
import fontFilledTtf from './FluentSystemIcons-Filled.ttf';
import fontFilledWoff from './FluentSystemIcons-Filled.woff';
import fontFilledWoff2 from './FluentSystemIcons-Filled.woff2';
import fontRegularTtf from './FluentSystemIcons-Regular.ttf';
import fontRegularWoff from './FluentSystemIcons-Regular.woff';
import fontRegularWoff2 from './FluentSystemIcons-Regular.woff2';
import fontOneSizeTtf from './FluentSystemIcons-Resizable.ttf';
import fontOneSizeWoff from './FluentSystemIcons-Resizable.woff';
import fontOneSizeWoff2 from './FluentSystemIcons-Resizable.woff2';
export const enum FontFile {
Filled = 0,
Regular = 1,
Resizable = 2
}
const FONT_FAMILY_MAP = {
[FontFile.Filled]: 'FluentSystemIconsFilled',
[FontFile.Regular]: 'FluentSystemIconsRegular',
[FontFile.Resizable]: 'FluentSystemIcons',
} as const;
const useStaticStyles = makeStaticStyles(`
@font-face {
font-family: ${FONT_FAMILY_MAP[FontFile.Filled]};
src: url(${JSON.stringify(fontFilledWoff2)}) format("woff2"),
url(${JSON.stringify(fontFilledWoff)}) format("woff"),
url(${JSON.stringify(fontFilledTtf)}) format("truetype");
}
@font-face {
font-family: ${FONT_FAMILY_MAP[FontFile.Regular]};
src: url(${JSON.stringify(fontRegularWoff2)}) format("woff2"),
url(${JSON.stringify(fontRegularWoff)}) format("woff"),
url(${JSON.stringify(fontRegularTtf)}) format("truetype");
}
@font-face {
font-family: ${FONT_FAMILY_MAP[FontFile.Resizable]};
src: url(${JSON.stringify(fontOneSizeWoff2)}) format("woff2"),
url(${JSON.stringify(fontOneSizeWoff)}) format("woff"),
url(${JSON.stringify(fontOneSizeTtf)}) format("truetype");
}
`)
const useRootStyles = makeStyles({
root: {
display: 'inline-block',
fontStyle: 'normal',
lineHeight: '1em',
"@media (forced-colors: active)": {
forcedColorAdjust: 'auto',
}
},
[FontFile.Filled]: {
fontFamily: 'FluentSystemIconsFilled',
},
[FontFile.Regular]: {
fontFamily: 'FluentSystemIconsRegular',
},
[FontFile.Resizable]: {
fontFamily: 'FluentSystemIcons',
},
});
export function createFluentFontIcon(displayName: string, codepoint: string, font: FontFile, fontSize?: number): React.FC<FluentIconsProps<React.HTMLAttributes<HTMLElement>>> {
const Component: React.FC<FluentIconsProps<React.HTMLAttributes<HTMLElement>>> = (props) => {
useStaticStyles();
const styles = useRootStyles();
props.className = mergeClasses(styles.root, styles[font], props.className);
const { primaryFill, ...state } = useIconState<React.HTMLAttributes<HTMLElement>>(props);
// We want to keep the same API surface as the SVG icons, so translate `primaryFill` to `color`
if (primaryFill) {
state.style = {
...state.style,
color: primaryFill
}
}
if (fontSize) {
state.style = {
...state.style,
fontSize
}
}
return <i {...state}>{codepoint}</i>
}
Component.displayName = displayName;
return Component;
}

14
packages/react-icons/src/utils/fonts/fontFile.types.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,14 @@
declare module '*.ttf' {
const uri: string;
export default uri;
}
declare module '*.woff' {
const uri: string;
export default uri;
}
declare module '*.woff2' {
const uri: string;
export default uri;
}

Просмотреть файл

@ -12,7 +12,7 @@ const useRootStyles = makeStyles({
}
});
export const useIconState = (props: FluentIconsProps): FluentIconsProps => {
export const useIconState = <TBaseAttributes extends (React.SVGAttributes<SVGElement> | React.HTMLAttributes<HTMLElement>) = React.SVGAttributes<SVGElement>>(props: FluentIconsProps<TBaseAttributes>): FluentIconsProps<TBaseAttributes> => {
const { title, primaryFill = "currentColor", ...rest } = props;
const state = {
...rest,
@ -34,5 +34,5 @@ export const useIconState = (props: FluentIconsProps): FluentIconsProps => {
state['role'] = 'img';
}
return state;
return state as unknown as FluentIconsProps<TBaseAttributes>;
};

Просмотреть файл

@ -10,7 +10,9 @@
"forceConsistentCasingInFileNames": true,
"strictNullChecks": true,
"moduleResolution": "node",
"jsx": "react"
"jsx": "react",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true
},
"include": ["src"],
"exclude": ["lib", "lib-cjs"]

1276
packages/svg-icons/package-lock.json сгенерированный

Разница между файлами не показана из-за своего большого размера Загрузить разницу