Skip to content

sxxov/maic

Repository files navigation

maic

Material Design Icons by Google, in a tree-shakable, SVG string, ESModule form. Based off of @material-design-icons/svg, & automatically updates via GitHub Actions.

<script>
	import { ic_done } from 'maic/two_tone';
	//       ^ '<svg xmlns="http://www.w3.org/2000/svg" ...'
</script>

<div class='component'>
	{@html ic_done}
</div>

Table of contents

  1. Structure
  2. Usage
    1. Variant-level import
    2. Icon-level import
    3. Top-level import
  3. Usage notes
    1. vite/rollup builds
    2. EMFILE errors on platforms with low file descriptor limits (i.e. Vercel)
    3. Tree shaking
    4. ESM comptibility
  4. Package notes
    1. Versioning
    2. Building
    3. License

Structure

The structure of importables are as so:

┌/maic
└─┬/{variant}
  └──/ic_{icon}

{variant} & {icon} correspond to the variant (eg. filled, outline, rounded, sharp, two_tone) of the icon, & the icon font ligature (eg. done, search, settings), respectfully. See the full list of both on Google Fonts or @material-design-icons/svg's demo.

This structure is exposed both in the module exported paths, as well as each level's import. This enables, but is not limited to, the following patterns:

Example

import * as maic from 'maic';
import { filled } from 'maic';
import * as filled from 'maic/filled';
import { ic_done } from 'maic/filled';
import ic_done from 'maic/filled/done';

Caveat 1

Unlike the folder structure found in @material-design-icons/svg, {variant} s here are snake_cased rather than kebab-cased (eg. two-tone is cased as two_tone).

For more info, see ESM compatibility.

Caveat 2

{icon} s are always prefixed with ic_ (eg. import done from 'ic_done').

For more info, see ESM compatibility.

Usage

Before you start using maic, ensure your bundler, import loader, or simply your JavaScript environment, performs NodeJS-style module resolution. This is the default for most popular bundlers, but you may have to enable it manually, even for NodeJS itself. If you aren't able to modify such a setting, you may have to modify your import specifiers (see below).

After that, simply use one of the import strategies below:

Variant-level import

Import the variant, & get access to each icon individually through named imports.

ℹ️ Hint

This is the recommended way of using maic, as it balances terseness with tree-shakability.

For more info, see Tree-shaking.

Example

import { ic_1k_plus } from 'maic/filled';
//       ^ '<svg xmlns="http://www.w3.org/2000/svg" ...'

ℹ️ Hint

If you're having trouble importing & you're not using NodeJS-style imports, try appending /index.js to the end of the import specifier (eg. maic/filledmaic/filled/index.js).

Icon-level import

Import only a specific icon, & gain access to it through a default import.

Use this when you're not using a bundler, or one that doesn't support tree-shaking. Also use this you're using dynamic imports (eg. await import('maic/filled/ic_1k_plus')).

For more info, see Tree-shaking.

import ic_1k_plus from 'maic/filled/ic_1k_plus';
//     ^ '<svg xmlns="http://www.w3.org/2000/svg" ...'

ℹ️ Hint

If you're having trouble importing & you're not using NodeJS-style imports, try appending .js to the end of the import specifier (eg. maic/filled/ic_1k_plusmaic/filled/ic_1k_plus.js).

Top-level import

Import the whole module, & gain access to variants, with their respective icons inside.

⚠️ Warning

Use this with caution! as to not "leak" the imported SVGs into un-tree-shakable contexts (such as cloning the immutable module object into a mutable, regular, JavaScript object)

For more info, see Tree-shaking.

Example

import { filled } from 'maic';
//	     ^ {
//		        ic_1k_plus: '<svg xmlns="http://www.w3.org/2000/...',
//		        ic_1k: '<svg xmlns="http://www.w3.org/2000/svg" ...',
//		        ...
//	       }

Usage notes

vite/rollup builds

To speed up development reloads when using vite/rollup, you may use the maic/helper plugin like below to only import the required icons during development.

Example

// vite/rollup.config.js
import maicHelper from 'maic/helper';

export default {
	plugins: [
		maicHelper({
			enabled: !process.env.NODE_ENV !== 'production',
		}),
	],
};

EMFILE errors on platforms with low file descriptor limits (i.e. Vercel)

maic is a large module, having a file for each icon, for each variant. On certain build pipelines, treeshaking doesn't apply for SSR builds & they leak overly wide maic imports, such as import {} from 'maic', causing runtimes to load the entire maic library & crashing. In this case, using maic/helper for both the development & production build may help.

Example

// vite/rollup.config.js
import maicHelper from 'maic/helper';

export default {
	plugins: [
		maicHelper(),
	],
};

Tree-shaking

The assumption of a working, tree-shaking-capable bundles is the crux of why this module can even exist. maic utilises the fact that any code that is not imported, or is imported & not used, will be shed away in modern bundlers. This enables maic to lump the (surprisingly massive) collection of Material Design Icon SVGs into a few JavaScript files & call it a day.

...Somewhat.

Unfortunately, tree-shaking in the JavaScript ecosystem is often fragile. This is due to the dynamism of JavaScript's interpreted nature. You can do really nasty things to access & assign properties (eg. eval). Due to this bundlers have a relatively limited scope of when & where tree-shaking happens. The gist of import-related rules are as follows:

  • ✔️ Import maps
    • (eg. import { ic_done } from 'maic/filled')
  • ✔️ Immutable imported objects
    • (eg. import * as filled from 'maic/filled')
  • ❌ Dynamic imports
    • (eg. const { ic_done } = await import('maic'))

In the situations where tree-shaking doesn't kick in, you may want to consider using only icon-level imports, or vite/rollup hot reloads. However, if the problem permuates through your codebase, a build chain refactor is commonly the only way out.

ESM compatibility

There have been a few changes that were carried out to enable ESM compatibility:

  • Transforming kebab-cased folder names to snake_cased
    • (eg. two-tone is not a valid JavaScript identifier, as the hyphen is the subtraction operator)
  • Appending the ic_ prefix to every icon
    • (eg. 1k is not a valid JavaScript identifier, as it starts with a number)

Both of these choices were made to allow imports to be consistent across folder structures & ESModule imports.

Example

// `two_tone` can be imported & is consistent with directory imports
// the `ic_` prefix appears everywhere instead of only certain places

import { ic_1k } from 'maic/filled';
import ic_1k_plus from 'maic/filled/ic_1k_plus';
import { two_tone } from 'maic';
import { ic_1x_mobiledata } from 'maic/two_tone';
import ic_2k_plus from 'maic/two_tone/ic_2k_plus';

Package notes

Versioning

The sematic version of maic is linked to @material-design-icons's. Any hotfixes to maic itself, orthogonal to the version of icons it depends on, will be appended to the end of the version (eg. the first fix for @material-design-icons@0.12.1 will be maic@0.12.1-1).

Building

The only prerequisites is having npm & NodeJS installed. Run the following to build the maic workspace, & publish it to npm:

npm run build
npm run publish

License