marella / material-icons

Latest icon fonts and CSS for self-hosting material design icons.
https://marella.github.io/material-icons/demo/
Apache License 2.0
305 stars 36 forks source link

Automatically generated typescript types #36

Closed robert-w-gries closed 1 year ago

robert-w-gries commented 1 year ago

Hello, would you be interested in a PR that generates an index.d.ts file with each release? The codepoints.js script would be extended to parse the the_data/codepoints.json file and generate a set of types. Something like:

export type MaterialIconSet = [
  ...
  "ac_unit",
  "access_alarm",
  "access_alarms",
  "access_time",
  "access_time_filled",
  "accessibility",
  "accessibility_new",
  "accessible",
  "accessible_forward",
  "account_balance",
  "account_balance_wallet",
  "account_box",
  "account_circle",
  "account_tree",
  ...
];

export type MaterialIcons = IconSet[number];

This feature would allow typescript apps to type check/autocomplete the icon names:

// remove 'account_box' and you can autocomplete a different icon name
const iconName: MaterialIcons = 'account_box';
marella commented 1 year ago

Hi, thanks for providing the example. I haven't used typescript so please help me understand the following:

  1. Can these types be published as a separate package e.g. @material-design-icons/types?

  2. Do types automatically get imported from index.d.ts when users install the package? Can this lead to type name collisions - if another library defines a type with same name will it lead to errors if users install both packages?

  3. Will the following work (renamed type names):

    // Will it work if we don't export this type?
    type MaterialIcons = [
      ...
    ];
    
    export type MaterialIcon = MaterialIcons[number];
robert-w-gries commented 1 year ago
  1. Can these types be published as a separate package e.g. @material-design-icons/types?

I believe you can publish a separate types package, but as the typescript publishing documentation explains, the two standard methods of publishing types are either including them in the npm package or publishing to the @types npm organization via DefinitelyTyped.

  1. Do types automatically get imported from index.d.ts when users install the package? Can this lead to type name collisions - if another library defines a type with same name will it lead to errors if users install both packages?

No, the types get manually imported using the import statement like in normal JS. In the rare chance someone already has the name MaterialIcon imported, you could do the following:

import { MaterialIcon } from './material-icon-component.jsx’;
import { MaterialIcon as MaterialIconType } from 'material-icons';
  1. Will the following work (renamed type names):

Absolutely, you can pick and choose what to export!

marella commented 1 year ago

Thanks.

For implementation, using icon names in versions.json is better as codepoints.json is generated from font files which contains extra ligatures which are not valid icons. The new script can be added as a new command to @material-design-icons/scripts so that it can be reused for material-symbols as well. A new types.js script can be added in the above directory which uses getVersions() function to get list of icons and generate the types file which looks something like:

// types.js
import * as fs from 'node:fs/promises';

import { getVersions } from './metadata.js';

// This function will be the entry point to the new command.
export const generateTypes = async (symbols, path) => {
  const versions = await getVersions(symbols);
  const types = getTypes(Object.keys(versions));
  await fs.writeFile(path, types);
}

const getTypes = (icons) => {
  // Create and return the `index.d.ts` file contents as string.
}

If you can implement the getTypes() function (takes list of icon names as input and returns the file contents as output), I can implement the rest to add it as a command.

Please feel free to let me know if you would like me to handle the full implementation.

robert-w-gries commented 1 year ago

I'd like to contribute the getTypes() function. Would probably save you a decent bit of time as you haven't used typescript before.

Would you also like a test or a demo typescript application PR? Or would you rather keep it simple?

marella commented 1 year ago

I have added boilerplate code to types.js which can be run using:

npx @material-design-icons/scripts generate types

Please send a PR for getTypes() function.

PR for demo app isn't required but I would like to know how typescript resolves index.d.ts. Currently users can import this package as:

import 'material-icons';

Main file is material-icons.css so above code imports CSS not JS. Will users be able to import types using:

import { MaterialIcon } from 'material-icons';

Will this correctly import typescript types from index.d.ts and not CSS from material-icons.css? Can this lead to duplicate imports of CSS?

robert-w-gries commented 1 year ago

Will this correctly import typescript types from index.d.ts and not CSS from material-icons.css? Can this lead to duplicate imports of CSS?

The typescript compiler will detect that we are importing only a type with import { MaterialIcon } from 'material-icons' and skip the emitting of require() for that particular import. The Typescript Handbook explains this in more detail.

I also made a demo program as a test:

// index.ts
import { MaterialIcon } from 'material-icons';
import 'material-icons';

const test: MaterialIcon = 'search';

The above file when compiled with tsc will emit the following js:

// index.js
"use strict";
exports.__esModule = true;
require("material-icons");
var test = 'search';
marella commented 1 year ago

Thanks for explaining.

marella commented 1 year ago

Thanks for the PRs.