JSPM

  • Created
  • Published
  • Downloads 7058
  • Score
    100M100P100Q118578F
  • License MIT

A drop-in replacement for native enum. Like native enum but much better!

Package Exports

  • enum-plus
  • enum-plus/enum
  • enum-plus/enum-item
  • enum-plus/enum-items
  • enum-plus/es-legacy
  • enum-plus/localize-interface
  • enum-plus/package.json
  • enum-plus/types
  • enum-plus/utils

Readme

English | 中文 | CHANGELOG

enum-plus

Like native enum, but much better!


npm latest version npm package minimized gzipped size npm downloads code coverage GitHub License

Supported Platforms

Node compatibility Web Browsers React Native MiniProgram Taro

⬇️  Introduction | Features | Installation | Enum Initialization | API | Static Methods | User Stories | Plugin System | Localization | Extensibility | Naming Conflicts | Best Practices | Compatibility | Q&A | Contributing  ⬇️

🎉 v3.0 is Released!

The new version is a major milestone that brings many exciting features and improvements. Please refer to the Release Notes and Migration Guide for details.

Introduction

enum-plus is an enhanced enumeration library that is fully compatible with the native enum and serves as a drop-in replacement library. It supports adding display text to enum items and extending more custom metadata fields. Enums can be generated into various UI components such as dropdowns and menus, which greatly simplifies front-end development.

It also provides many extension methods for iterating over enum items and data transformations. You can convert values directly into names in the current language, since it supports internationalization. It's very useful for displaying business data in the UI.

It is a lightweight, zero-dependency library, and it works with any front-end framework, including vanilla projects.

What other exciting features are there? Please continue to explore! Or you can check out this video first.

usage video

Here are some hot questions, feel free to check them out

Features

  • Fully compatible with native enum usage
  • Supports multiple data types such as number and string
  • Enhanced enum items with display names
  • Internationalization support for display names, can be integrated with any i18n library
  • Converts values directly into display names, simplifying data display in the UI
  • Extensible design, allowing custom metadata fields for enum items
  • Plugin system design, extending enum functionality through plugin installations
  • Supports type narrowing to enhance type safety  TypeScript
  • Generates dropdowns from enums, compatible with UI libraries like Ant Design, Element Plus, Material-UI and more
  • Supports server-side rendering (SSR)
  • Compatible with various environments including Browsers, Node.js, React Native, Taro, and mini-programs
  • Compatible with any front-end development framework, including vanilla projects
  • TypeScript‑oriented, providing excellent type inference and code completion capabilities
  • Zero dependencies
  • Lightweight (only 2KB+ minzipped)

Installation

Install using npm:

npm install enum-plus

Install using pnpm:

pnpm add enum-plus

Install using bun:

bun add enum-plus

Or using yarn:

yarn add enum-plus

Zero‑Install Usage:

  • The specific version:
<!-- Modern version, ES2020 compatible -->
<script src="https://cdn.jsdelivr.net/npm/enum-plus@v3.0.0-beta.4/script>
<!-- Legacy version, ES2015 compatible -->
<script src="https://cdn.jsdelivr.net/npm/enum-plus@v3.0.0-beta.4/script>
  • The latest version:
<!-- Modern version, ES2020 compatible -->
<script src="https://cdn.jsdelivr.net/npm/enum-plus/umd/enum-plus.min.js"></script>
<!-- Legacy version, ES2015 compatible -->
<script src="https://cdn.jsdelivr.net/npm/enum-plus/umd/enum-plus-legacy.min.js"></script>

⬇️ Download:

You can also download these files from the GitHub Releases.

Enum Initialization

This section shows the various ways to initialize enums using the Enum function. Understanding these different initialization formats allows you to choose the most convenient approach for your specific use case.

1. Simple Key-Value Format

The simplest format is a direct mapping of keys to values. This is similar to the native enum format.

import { Enum } from 'enum-plus';

// With number values
const WeekEnum = Enum({
  Sunday: 0,
  Monday: 1,
});

WeekEnum.Monday; // 1

// With string values
const WeekEnum2 = Enum({
  Sunday: 'Sun',
  Monday: 'Mon',
});

WeekEnum2.Monday; // 'Mon'

If your project uses TypeScript and the version is below 5.0, it is recommended to add the as const type assertion to the parameter of the Enum method. In this way, the enum values will remain their original literal values instead of being converted to number or string. For more details, please refer to here.

The standard format includes both a value and a label for each enum item. This is the most commonly used format and is recommended for most cases. This format allows you to specify a display name for each enum item, which can be used in UI components. Please refer to the Localization section for internationalization support.

import { Enum } from 'enum-plus';

const WeekEnum = Enum({
  Sunday: { value: 0, label: 'I love Sunday' },
  Monday: { value: 1, label: 'I hate Monday' },
});

WeekEnum.Sunday; // 0
WeekEnum.label(0); // I love Sunday

3. Label-Only Format

This is useful when you want to use the key as the value.

import { Enum } from 'enum-plus';

const WeekEnum = Enum({
  Sunday: { label: 'I love Sunday' },
  Monday: { label: 'I hate Monday' },
});

WeekEnum.Sunday; // 'Sunday'
WeekEnum.label('Sunday'); // I love Sunday

4. Array Format

The array format is useful when you need to create enums dynamically, such as from API data. This allows for flexibility in Custom Field Mapping to adapt to different data structures.

import { Enum } from 'enum-plus';

const pets = [
  { value: 1, key: 'Dog', label: 'Dog' },
  { value: 2, key: 'Cat', label: 'Cat' },
  { value: 3, key: 'Rabbit', label: 'Rabbit' },
];
const PetEnum = Enum(pets);

PetEnum.Dog; // 1
PetEnum.label(1); // Dog

5. Native Enum Format

You can also create from native enums. It benefits from the native enum's auto-incrementing behavior.

import { Enum } from 'enum-plus';

enum init {
  Sunday = 0,
  Monday,
  Tuesday,
  Wednesday,
  Thursday,
  Friday,
  Saturday,
}
const WeekEnum = Enum(init);

WeekEnum.Sunday; // 0
WeekEnum.Monday; // 1
WeekEnum.Saturday; // 6

API

💎   Picks an enum value

Enum.XXX

Works like native enum, allowing you to access enum values directly.

WeekEnum.Sunday; // 0
WeekEnum.Monday; // 1

💎   named

Record<string, EnumItemClass>

An object that aggregates all enum items, allowing quick access to a specific enum item object through its key.

WeekEnum.named.Monday; // { key: 'Monday', value: 1, label: 'Monday' }

💎   items

{ value, label, key, raw }[]

Returns a read-only array of all enum items.

WeekEnum.items; // [ { value: 0, label: 'Sunday', key:  'Sunday' }, { value: 1, label: 'Monday', key: 'Monday' }, ... ]

💎   values

(string | number)[]

Returns an array of all enum item value(s).

WeekEnum.values; // [0, 1, 2, 3, 4, 5, 6]

💎   labels

string[]

Returns an array of all enum item label(s).

WeekEnum.labels; // ['Sunday', 'Monday', ... 'Friday', 'Saturday']

💎   keys

string[]

Returns an array of all enum item key(s)

WeekEnum.keys; // ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']

💎   meta

Record<string, any[]>

Returns an object containing arrays of custom field values for all enum items. Each key in the object corresponds to a custom field name, and the value is an array of that field's values across all enum items.

const ColorEnum = Enum({
  Red: { value: 1, label: 'Red', hex: '#FF0000' },
  Green: { value: 2, label: 'Green', hex: '#00FF00' },
  Blue: { value: 3, label: 'Blue', hex: '#0000FF' },
});
ColorEnum.meta.hex; // ['#FF0000', '#00FF00', '#0000FF']

By the way, you can quickly access custom fields of a single enum item through the named property.

ColorEnum.named.Red.raw.hex; // '#FF0000'

💎   has

[F]   has(keyOrValue?: string | number): boolean

Determine whether a certain enum item (value or key) exists.

WeekEnum.has(1); // true
WeekEnum.has('Sunday'); // true
WeekEnum.has(9); // false
WeekEnum.has('Birthday'); // false

💎   findBy

[F]   findBy(field: string, value: any): EnumItemClass | undefined

Find an enum item by a specific field and its value. Returns the enum item object if found, otherwise returns undefined.

The field parameter can be one of the built-in fields: key, value, label, or any custom field defined in the enum.

WeekEnum.findBy('value', 1); // { key: 'Monday', value: 1, label: 'Monday' }
WeekEnum.findBy('key', 'Monday'); // { key: 'Monday', value: 1, label: 'Monday' }

If you want to get the custom fields of a known enum item, it is recommended to use the named property to access it


💎   label

[F]   label(keyOrValue?: string | number): string | undefined

Gets the display name of an enum item based on a certain value or key. If localization has been set up, the localized text will be returned.

WeekEnum.label(1); // Monday
WeekEnum.label('Monday'); // Monday, here is label, not key

💎   key

[F]   key(value?: string | number): string | undefined

Get the key of an enum item based on the enum value, if the key is not found, return undefined.

WeekEnum.key(1); // Monday (here is key, not label)

💎   raw

[F^1]   raw(): Record<K, T[K]>
[F^2]   raw(keyOrValue: V | K): T[K]

The raw method is used to return the original initialization object of the enum collection, which is the object used to create the enum.

The second overload method is used to return the original initialization object of a single enum item.

The main purpose of the raw method is to get the extended custom fields of the enum items. Unlimited number of custom fields are allowed.

const WeekEnum = Enum({
  Sunday: { value: 0, label: 'Sunday', happy: true },
  Monday: { value: 1, label: 'Monday', happy: false },
});

WeekEnum.raw(0).happy; // true
WeekEnum.raw(0); // { value: 0, label: 'Sunday', happy: true }
WeekEnum.raw('Monday'); // { value: 1, label: 'Monday', happy: false }
WeekEnum.raw(); // { Sunday: { value: 0, label: 'Sunday', happy: true }, Monday: { value: 1, label: 'Monday', happy: false } }

💎   toList

[F^1]   toList(): { value, label }[]
[F^2]   toList(options?: { valueField?: string; labelField?: string }): { [key: string]: any }[]

Converts the enum items to an array of objects, each containing value and label fields by default. You can customize the field names using the options parameter.

WeekEnum.toList();
// [
//   { value: 1, label: 'Monday' },
//   { value: 2, label: 'Tuesday' },
//   ...
// ]
WeekEnum.toList({ valueField: 'id', labelField: 'name' });
// [
//   { id: 1, name: 'Monday' },
//   { id: 2, name: 'Tuesday' },
//   ...
// ]

💎   toMap

[F^1]   toMap(): Record<string, string | number>
[F^2]   toMap(options?: { keySelector?: string; valueSelector?: string }): Record<string, any>

Converts the enum items to a key-value map object, where the keys are the enum values and the values are the enum labels by default. You can customize the key and value fields using the options parameter.

WeekEnum.toMap();
// {
//   "1": 'Monday',
//   "2": 'Tuesday',
//   ...
// }
WeekEnum.toMap({ keySelector: 'key', valueSelector: 'value' });
// {
//   "Monday": 1,
//   "Tuesday": 2,
//   ...
// }

💎   name

string

Display name for Enum types. When creating an enum, you can assign it a display name using the optional name parameter. This name can be either a plain string or a localization key to support internationalized text. Please refer to the Localization section for more details.

Usually Enums are used to generate dropdown menus in forms, or show item text in table cells. The display name of the enum type often serves as the form field label or table caption. By utilizing the name property, you can centralize the management of both the enum type's display name and its items' labels, simplifying maintenance and ensuring consistency across your application.

const WeekEnum = Enum(
  {
    Sunday: { value: 0, label: 'Sunday', happy: true },
    Monday: { value: 1, label: 'Monday', happy: false },
  },
  {
    name: 'i18n.enums.week', // A localization key
  }
);

WeekEnum.name; // Week
WeekEnum.label(0); // Sunday
WeekEnum.label(1); // Monday

⚡️   valueType   [TypeScript Only]

value1 | value2 | ...

In TypeScript, provides a union type containing all enum values, enabling precise type constraints for variables and component properties. This replaces broad primitive types like number or string with exact value sets, preventing invalid assignments while enhancing both code readability and compile-time type safety.

type WeekValues = typeof WeekEnum.valueType; // 0 | 1

const weekValue: typeof WeekEnum.valueType = 1; // ✅ Type correct, 1 is a valid week enum value
const weeks: (typeof WeekEnum.valueType)[] = [0, 1]; // ✅ Type correct, 0 and 1 are valid week enum values
const badWeekValue: typeof WeekEnum.valueType = 8; // ❌ Type error, 8 is not a valid week enum value
const badWeeks: (typeof WeekEnum.valueType)[] = [0, 8]; // ❌ Type error, 8 is not a valid week enum value

Note: This is a TypeScript type only and cannot be called at runtime. Calling it at runtime will throw an exception.


⚡️   keyType   [TypeScript Only]

key1 | key2 | ...

Similar to valueType, provides a union type of all enum key(s)

type WeekKeys = typeof WeekEnum.keyType; // 'Sunday' | 'Monday'

const weekKey: typeof WeekEnum.keyType = 'Monday';
const weekKeys: (typeof WeekEnum.keyType)[] = ['Sunday', 'Monday'];

Note: This is a TypeScript type only and cannot be called at runtime. Calling it at runtime will throw an exception.


⚡️   rawType   [TypeScript Only]

{ value: V, label: string, [...] }

Provides a type of the original initialization object of the Enum collection.

type WeekRaw = typeof WeekEnum.rawType;
// { Sunday: { value: 0, label: string }, Monday: { value: 1, label: string } }

Note: This is a TypeScript type only and cannot be called at runtime. Calling it at runtime will throw an exception.


Static Methods

💎   Enum.isEnum

[F]   isEnum(obj: any): boolean

Check if a given object is an instance created by the Enum function.

Enum.isEnum(WeekEnum); // true
Enum.isEnum({}); // false

💎   Enum.localize

[F]   (key: string) => string

Set a global localization function for all enums. This function will be used to get the localized text for enum items and enum type names. Please refer to the Localization section for more details.

import i18n from 'i18next';

Enum.localize = (key) => i18n.t(key);

💎   Enum.extends

[F]   (obj: Record<string, unknown> | undefined) => void

Extend the Enum objects with custom methods. More details can be found in the Extensibility section.

Enum.extends({
  sayHello() {
    return `Hello EnumPlus!`;
  },
});

💎   Enum.install

[F]   (plugin: Plugin, options?: any) => void

Install a plugin to extend the functionality of all enums. More details can be found in the Plugin System section.

import i18nextPlugin from '@enum-plus/plugin-i18next';

Enum.install(i18nextPlugin);

User Stories

💡 Prevent Magic Numbers

const WeekEnum = Enum({
  Sunday: { value: 0, label: 'Sunday' },
  Monday: { value: 1, label: 'Monday' },
});

if (today === WeekEnum.Sunday) {
  // It's Sunday, enjoy your day!
} else if (today === WeekEnum.Monday) {
  // Oh no, it's Monday again...
}

💡 Check for Valid Enum Values

if (WeekEnum.has(value)) {
  // It's a valid enum value, safe to use
} else {
  // Throw an error or use a default value
}

💡 Check for Enum Objects

let values: number[] | undefined;
if (Enum.isEnum(data)) {
  values = data.values;
} else if (Array.isArray(data)) {
  values = data;
} else {
  // Throw an error or use a default value
}

💡 Generate UI Controls

Take React and Ant Design as an example. Please refer to the Supports Various Frontend Frameworks section for more examples.

import { Menu, Select, Table } from 'antd';
import { ProFormCheckbox, ProFormSelect } from '@ant-design/pro-components';

const App = () => {
  return (
    <>
      <Select options={WeekEnum.items} />
      <Menu items={WeekEnum.toMenu()} />
      <Table columns={[{ filters: WeekEnum.toFilter() }]} />
      <ProFormSelect valueEnum={WeekEnum.toValueMap()} />
      <ProFormCheckbox valueEnum={WeekEnum.toValueMap()} />
    </>
  );
};

Need to install @enum-plus/plugin-antd plugin


💡 Internationalization for Enum Names and Labels

Support internationalization. Set the label field to a localization key, so that it displays the corresponding text based on the current language environment. Please refer to the Localization section for more details.

WeekEnum.label(1); // Monday or 星期一, depending on the current locale
WeekEnum.named.Monday.label; // Monday or 星期一, depending on the current locale
WeekEnum.name; // Week or 周, depending on the current locale

💡 Type Narrowing   [TypeScript Only]

type MyComponentProps = {
  day: typeof WeekEnum.valueType; // 0 | 1 | ... | 5 | 6
};
const MyComponent = (props: MyComponentProps) => {
  return <div>Today is {WeekEnum.label(props.day)}</div>;
};

<MyComponent day={1} />; // ✅ Type correct
<MyComponent day="Monday" />; // ❌ Type error
<MyComponent day={8} />; // ❌ Error, 8 is not a valid enum value
function setDay(day: typeof WeekEnum.valueType) {
  // The type of day is narrowed to 0 | 1 | ... | 5 | 6
}

setDay(1); // ✅ Type correct
setDay('Monday'); // ❌ Type error
setDay(8); // ❌ Error, 8 is not a valid enum value

💡 Support Metadata Fields That Can Serve as a Static Configuration System

const ColorEnum = Enum({
  Red: { value: 1, hex: '#FF0000', icon: '🔥' },
  Green: { value: 2, hex: '#00FF00', icon: '🍏' },
  Blue: { value: 3, hex: '#0000FF', icon: '🔵' },
});

ColorEnum.values; // [1, 2, 3]
ColorEnum.keys; // ['Red', 'Green', 'Blue']
ColorEnum.meta.hex; // ['#FF0000', '#00FF00', '#0000FF']
ColorEnum.meta.icon; // ['🔥', '🍏', '🔵']
ColorEnum.named.Red.raw.hex; // '#FF0000'
ColorEnum.named.Red.raw.icon; // '🔥'

Supports Traversing Enum Items in a Read-Only Manner

Array.isArray(WeekEnum.items); // true
WeekEnum.items.map((item) => item.value); // [0, 1, ..., 5, 6]
WeekEnum.items.forEach((item) => {
  // ✅ Traversable
});
for (const item of WeekEnum.items) {
  // ✅ Traversable
}

WeekEnum.items.push({ value: 2, label: 'Tuesday' }); // ❌ Not allowed, read-only
WeekEnum.items.splice(0, 1); // ❌ Not allowed, read-only
WeekEnum.items[0].label = 'foo'; // ❌ Not allowed, read-only

💡 Enum Composition (Merging)

const PrimaryColorEnum = Enum({
  Red: { value: 1, hex: '#FF0000' },
  Green: { value: 2, hex: '#00FF00' },
  Blue: { value: 3, hex: '#0000FF' },
});
const SecondaryColorEnum = Enum({
  Yellow: { value: 4, hex: '#FFFF00' },
  Cyan: { value: 5, hex: '#00FFFF' },
  Magenta: { value: 6, hex: '#FF00FF' },
});
const AllColorEnum = Enum({
  ...PrimaryColorEnum.raw(),
  ...SecondaryColorEnum.raw(),
});

💡 Supports JSDoc Comments on Enum Items, Enabling Code Intelligence

Supports inline documentation through JSDoc, allowing engineers to view detailed comments by simply hovering over enum values in the editor. Please refer to the Best Practices section for writing good code.

const WeekEnum = Enum({
  /** Represents Sunday */
  Sunday: { value: 0, label: 'Sunday' },
  /** Represents Monday */
  Monday: { value: 1, label: 'Monday' },
});

WeekEnum.Monday; // Hover over Monday

jsdoc

You can see that this hover functionality reveals both documentation and enum values simultaneously, without leaving your current position in the code.


💡 Supports Various Frontend Frameworks

Enum.items can be consumed as control data sources (using Select as an example).

  • React-based UI libraries

    Ant Design | Arco Design Select

    import { Select } from 'antd';
    
    <Select options={WeekEnum.items} />;

    Material-UI Select

    import { MenuItem, Select } from '@mui/material';
    
    <Select>
      {WeekEnum.items.map((item) => (
        <MenuItem key={item.value} value={item.value}>
          {item.label}
        </MenuItem>
      ))}
    </Select>;

    Kendo UI Select

    import { DropDownList } from '@progress/kendo-react-dropdowns';
    
    <DropDownList data={WeekEnum.items} textField="label" dataItemKey="value" />;
  • Vue-based UI libraries

    Element Plus Select

    <el-select>
      <el-option v-for="item in WeekEnum.items" v-bind="item" />
    </el-select>

    Ant Design Vue | Arco Design Select

    <a-select :options="WeekEnum.items" />

    Vuetify Select

    <v-select :items="WeekEnum.items" item-title="label" />
  • Angular-based UI libraries

    Angular Material Select

    <mat-select>
      @for (item of WeekEnum.items; track item.value) {
        <mat-option [value]="item.value">{{ item.label }}</mat-option>
      }
    </mat-select>

    NG-ZORRO Select

    <nz-select>
      @for (item of WeekEnum.items; track item.value) {
        <nz-option [nzValue]="item.value">{{ item.label }}</nz-option>
      }
    </nz-select>

💡 Custom Field Mapping in Array Format Initialization

In 4. Array Format section, we know that you can build an enum from dynamic data from the backend, but it is very likely that the field names of dynamic data are not value, label, key, but other field names. In this case, you can pass in a custom option to map these to other field names.

import { Enum } from 'enum-plus';

const data = await getPetsData();
// [   { id: 1, code: 'dog', name: 'Dog' },
//     { id: 2, code: 'cat', name: 'Cat' },
//     { id: 3, code: 'rabbit', name: 'Rabbit' }   ];
const PetTypeEnum = Enum(data, {
  getValue: 'id',
  getLabel: 'name',
  getKey: 'code', // getKey is optional
});
PetTypeEnum.items; // The output is:
// [   { value: 1, label: 'Dog', key: 'dog' },
//     { value: 2, label: 'Cat', key: 'cat' },
//     { value: 3, label: 'Rabbit', key: 'rabbit' }   ]

getValue, getLabel, getKey can also be a function to handle more complex business logic, for example:

const PetTypeEnum = Enum(petTypes, {
  getValue: (item) => item.id,
  getLabel: (item) => `${item.name} (${item.code})`,
  getKey: (item) => item.code,
});

Plugin System

enum-plus provides a plugin system that allows you to extend the functionality of all enums. Plugins can add new methods or properties to all enum instances, greatly enhancing their capabilities. You can choose to install only the plugins you need, keeping the core library lightweight and efficient.

import antdPlugin from '@enum-plus/plugin-antd';
import { Enum } from 'enum-plus';

Enum.install(antdPlugin);

After installing a plugin, it will add new methods or properties to all enum instances. For example, after installing the @enum-plus/plugin-antd plugin, you can use the enum.toSelect method to generate a Select component from the enum.

Optionally, you can provide configuration options to customize the behavior of the plugin. For details on the configuration options for each plugin, please refer to the documentation of the respective plugins.

import antdPlugin from '@enum-plus/plugin-antd';
import { Enum } from 'enum-plus';

Enum.install(antdPlugin, {
  toSelect: {
    valueField: 'id', // Sets the field representing the value in the data object generated by the toSelect method
    labelField: 'name', // Sets the field representing the label in the data object generated by the toSelect method
  },
});

Awesome Plugins

The following plugins are available. You can choose to install them based on your needs:

  • @enum-plus/plugin-antd: Ant Design oriented features, including enum.toSelect, enum.toMenu, enum.toFilter, and enum.toValueMap. With these methods, you can directly bind enums to the corresponding Ant Design components, greatly simplifying your code.
  • @enum-plus/plugin-i18next: Automatically adapts to i18next to enable internationalization support for enums.
  • @enum-plus/plugin-react-i18next: Automatically adapts to react-i18next to enable internationalization support for enums.
  • @enum-plus/plugin-react: React integration, including support for Enum.localize to return React components, and listening for language changes to auto re-render components.
  • We are working on the following plugins:
    • @enum-plus/plugin-vue: Vue integration, including support for Enum.localize to return Vue components, and listening for language changes to auto re-render components.
    • @enum-plus/plugin-angular: Angular integration, including support for Enum.localize to return Angular components, and listening for language changes to auto re-render components. We need your help to develop this plugin!

If the plugin you are searching for is not available, or if you want to develop your own plugin, please refer to the Plugin Development Guide. You can develop new plugins in the official enum-plus repository or publish your developed plugins to npm and share your plugin links here. We sincerely need your help to enrich the plugin ecosystem!


Localization

enum-plus does not include built-in internationalization capabilities by default. Therefore, the label field of enum items is treated as a plain string and returns the original text directly.

To add localization support to enum-plus, the simplest way is to install the corresponding i18n plugin, such as @enum-plus/plugin-i18next, which automatically passes the values of the label and name fields to i18next for translation.

npm install @enum-plus/plugin-i18next i18next

Then install the plugin in the project entry file:

index.js

import i18nextPlugin from '@enum-plus/plugin-i18next';
import { Enum } from 'enum-plus';

Enum.install(i18nextPlugin);

After installing the plugin, the label and name fields of the enum will be automatically translated through i18next.

const WeekEnum = Enum(
  {
    Sunday: { value: 0, label: 'week.sunday' },
    Monday: { value: 1, label: 'week.monday' },
  },
  { name: 'weekDays.name' }
);
WeekEnum.label(1); // Monday or 星期一, depending on the current locale
WeekEnum.named.Monday.label; // Monday or 星期一, depending on the current locale
WeekEnum.name; // Week or 周, depending on the current locale

This plugin also supports custom i18next options, and even allows complete control over the localize method. Please refer to the plugin documentation for more details.

If you need to automatically update the UI after switching languages, this requires the capabilities of frameworks like React, Vue, or Angular. Please consider using plugins such as @enum-plus/plugin-react or @enum-plus/plugin-vue.

If you are using other internationalization libraries, such as react-intl, vue-i18next, or ngx-translate, you can integrate these libraries through the Enum.localize method.

my-extension.js

import { Enum } from 'enum-plus';

Enum.localize = (key) => {
  // This is a pseudo code, please adjust it according to your own logic
  return intl.formatMessage({ id: key });
};

Once you have completed this feature, it is recommended that you consider publishing it as an npm package and share it in the Awesome Plugins section, so that others can benefit from your work. If you believe that this project is very general, you can also consider submitting it to the official enum-plus plugin repository. For specific development rules, please refer to the Plugin Development Guide.


Extensibility

Enum provides a wealth of built-in methods and properties that can satisfy most common use cases. However, if they are not sufficient, you can use Enum.extends to add more custom methods. These extensions will be globally applied to all enum instances, including those created before the extension was applied, and will take effect immediately without any additional setup.

In fact, the entire Plugin System and the Enum.install method are implemented using Enum.extends at the underlying level.

  • TypeScript Projects

    my-enum-extension.ts

    // Implementation code
    Enum.extends({
      toMySelect() {
        return this.items.map((item) => ({ value: item.value, title: item.label }));
      },
      reversedItems() {
        return this.items.toReversed();
      },
    });
    
    // Type declaration for better type hints
    declare module 'enum-plus/extension' {
      export interface EnumExtension<T, K, V> {
        toMySelect: () => { value: V; title: string }[];
        reversedItems: () => EnumItemClass<EnumItemInit<V>, K, V>[];
      }
    }

    index.ts

    Then import this file in the entry file of your project:

    import './my-enum-extension';
    
    WeekEnum.toMySelect(); // [{ value: 0, title: 'Sunday' }, { value: 1, title: 'Monday' }]
  • JavaScript Projects

    my-enum-extension.js

    import { Enum } from 'enum-plus';
    
    // Implementation code
    Enum.extends({
      toMySelect() {
        return this.items.map((item) => ({ value: item.value, title: item.label }));
      },
      reversedItems() {
        return this.items.toReversed();
      },
    });

    my-enum-extension.js.d.ts

    import { EnumExtension, EnumItemClass, EnumItemInit } from 'enum-plus';
    
    // Type declaration for better type hints
    declare module 'enum-plus/extension' {
      export interface EnumExtension<T, K, V> {
        toMySelect: () => { value: V; title: string }[];
        reversedItems: () => EnumItemClass<EnumItemInit<V>, K, V>[];
      }
    }

    index.js

    Then import this file in the entry file of your project:

    import './my-enum-extension';
    
    WeekEnum.toMySelect(); // [{ value: 0, title: 'Sunday' }, { value: 1, title: 'Monday' }]

Please note that EnumExtension is a generic interface that accepts three type parameters, which represent:

  • T: Initialization object of the enum type (e.g., the object passed to Enum())
  • K: Key of the enum item (e.g., Sunday, Monday)
  • V: Value of the enum items

If you want to provide more friendly type hints in the extension methods, you may need to use these type parameters. However these are all optional, if you don't need them, you can omit them.


Naming Conflicts?

enum-plus is designed with naming conflicts in mind. The namespace of enum items is separate from the methods and properties of the enum instance, minimizing the chances of conflicts. For example, when an enum item's name conflicts with a method name, you can access the overridden methods through the items property.

import { KEYS, VALUES } from 'enum-plus';

const WeekEnum = Enum({
  foo: { value: 1 },
  bar: { value: 2 },
  keys: { value: 3 }, // Naming conflict
  values: { value: 4 }, // Naming conflict
  label: { value: 5 }, // Naming conflict
  named: { value: 6 }, // Naming conflict
  toList: { value: 7 }, // Naming conflict
});

WeekEnum.foo; // 1
WeekEnum.bar; // 2
// Below are all enum items, which take precedence and override the original methods
WeekEnum.keys; // 3
WeekEnum.values; // 4
WeekEnum.label; // 5
WeekEnum.named; // 6
WeekEnum.toList; // 7

// You can access these overridden methods via .items 🙂
WeekEnum.items[KEYS]; // ['foo', 'bar', 'keys', 'values', 'label', 'named', 'toList']
WeekEnum.items[VALUES]; // [1, 2, 3, 4, 5, 6, 7]
WeekEnum.items.label(1); // 'foo'
WeekEnum.items.named.foo; // { value: 1, label: 'foo', key: 'foo' }
WeekEnum.items.toList(); // [{ value: 1, label: 'foo' }, ...]

Note that keys and values are special because they are built-in methods of JavaScript arrays. To avoid altering the behavior of the items array, you need to use the KEYS and VALUES symbols as aliases to access them.

For an even more extreme case, what if items conflicts with an enum item name? Don't worry, you can still access it via the ITEMS alias.

import { ITEMS } from 'enum-plus';

const WeekEnum = Enum({
  foo: { value: 1 },
  bar: { value: 2 },
  items: { value: 3 }, // Naming conflict
  toList: { value: 4 }, // Naming conflict
});

WeekEnum.items; // 3, enum item takes precedence and overrides items
WeekEnum[ITEMS].toList(); // But you can access it via the ITEMS alias

Best Practices

When using enum-plus, following these best practices can help ensure consistency, maintainability, and clarity in your codebase:

  1. Enum Type Naming: Use PascalCase and append with the Enum suffix (e.g., WeekEnum, ColorEnum).
  2. Enum Item Naming: Use PascalCase for enum items (e.g., WeekEnum.Sunday, ColorEnum.Red). This naming style highlights the immutability and static nature of enum items, and ensures they appear at the top in IDE IntelliSense suggestions for easier selection.
  3. Semantic Clarity: Ensure enum and item names have clear semantics. Good semantic naming serves as self-documentation, making code intent explicit and reducing cognitive overhead.
  4. Single Responsibility Principle: Each enum type should represent a single, cohesive set of related constants. Avoiding overlapping responsibilities between different enum types.
  5. Provide JSDoc Comments: Provide JSDoc comments for each enum item and the enum type itself, explaining their purpose and usage. Comprehensive documentation enables IDE hover tooltips and improves code readability and maintainability.
  6. Internationalization Architecture: Plan for internationalization from the outset by leveraging the library's localization features. A well-designed internationalization architecture minimizes future refactoring and facilitates global scalability.

Here is an example that combines the above best practices to define an enum:

/** Represents the days of the week */
const WeekEnum = Enum(
  {
    /** Sunday */
    Sunday: { value: 0, label: 'enums.week.sunday' },
    /** Monday */
    Monday: { value: 1, label: 'enums.week.monday' },
    // ...
    /** Friday */
    Friday: { value: 5, label: 'enums.week.friday' },
    /** Saturday */
    Saturday: { value: 6, label: 'enums.week.saturday' },
  },
  { name: 'enums.week.name' }
);

Compatibility

enum-plus is designed to be compatible with a wide range of environments, including modern browsers, Node.js, and various build tools. Below are the compatibility details for different environments:

Browser Environments

  • Modern Bundlers: For bundlers supporting the exports configuration (such as webpack 5+, vite, rollup), imports will be resolved to the es directory, which targets ES2020.

  • Legacy Bundlers: For older bundlers without exports support (like Webpack 4), imports will be resolved to the es-legacy directory, which targets ES2015.

  • UMD Version: For direct browser usage or static projects without bundlers, enum-plus provides UMD format files in the umd directory. You can include it via a <script> tag and access it through the global window.EnumPlus. The UMD directory offers two versions:

    • enum-plus.min.js: Targets ES2020, suitable for modern browsers.
    • enum-plus-legacy.min.js: Targets ES2015, suitable for older browsers.
  • Polyfill Strategy: enum-plus ships no polyfills to minimize bundle size. For legacy browser support, you can choose from the following polyfill strategies based on your project's needs:

    • core-js
    • @babel/preset-env with appropriate useBuiltIns settings
    • Alternative polyfill implementations

Node.js Environments

In Node.js environments, you can import enum-plus using either require or import syntax.

  • require

    For all Node.js versions that support CommonJS, you can import enum-plus using require('enum-plus'). The require statement will be resolved to the lib directory, which targets ES2015. The minimum compatible version is Node.js v7.x.

  • import

    For modern versions of Node.js that support ES Modules (Node.js 14.13+), you can import enum-plus using import { Enum } from 'enum-plus'. The imports will be resolved to the es directory, which targets ES2020.


Q&A

Why do I need this library? TypeScript already has the built-in enums

TypeScript's built-in enum only provides the basic functionality of Enumeration: eliminating magic numbers, and regulating control flow. However, as a front-end engineer, the needs for enumerations are not merely these. We also need:

  1. Eliminate magic numbers
  2. Used in the if or switch statements for conditional branching
  3. Add display names to enums, and should support internationalization
  4. Add custom metadata fields, such as color, icon, and description, etc.
  5. Enums can be generated into various form controls such as dropdowns, menus, tabs, etc.
  6. Convert values directly into localized names for displaying business data in the UI

If you need these features, then enum-plus is designed for you. If you are a front-end engineer, we strongly recommend you give it a try!

It seems that TypeScript is going to deprecate enum?

Whether the enum feature will be replaced in the future or not, the concept of enumeration will never disappear. It is one of the most basic features in many high-level languages.

enum-plus was precisely created to make up for the shortcomings of TypeScript's built-in enum. It is a pure runtime library and will not be affected by the development of the TypeScript language. Therefore, you can use it with complete confidence. It will neither become outdated nor be deprecated in the future.

The TypeScript team does not have a clear plan to deprecate enum. However, it is indeed not supported in some cases. The key reason is that enum is neither a pure TypeScript type (which can be completely removed during compilation) nor pure JavaScript runtime code, but a mixture of the two, which brings significant complexity to the compiler.

How about the performance of this library?

enum-plus always focuses on performance. One of its design goals is to maintain efficient performance while providing rich functionality.

For the basic usage like WeekEnum.Monday, the performance is the same as the native enum, because they both directly access the member fields of a JavaScript object at the underlying level.

For operations such as traversing or searching the array of enum items, the performance is the same as that of native arrays, because the underlying level of the Enum collection is a frozen native array.

As you can see, the performance has almost reached its peak, and you can use it with complete confidence without worrying about performance issues.

Why doesn't Enum support reverse mapping?

Please use the enum.key(value) method to get the key name according to its value. This reverse mapping method is applicable to both numeric and string enum values.

Why does the search function of the Ant Design Select stop working after enabling internationalization?

This is because Enum.localize returns a component instance instead of a regular string, causing Ant Design to fail in performing string matching correctly. Please use the enum.isMatch method to enable the search functionality. Please refer to @enum-plus/plugin-react for more details.

npm install @enum-plus/plugin-react
import { Select } from 'antd';

<Select options={WeekEnum.items} filterOption={WeekEnum.isMatch} />;

If you are using the @enum-plus/plugin-i18next plugin, or have implemented the Enum.localize method yourself and it returns a string, then the search functionality in the dropdown should work correctly.

Do I have to install TypeScript? What if my project is in JavaScript?

Don't worry, whether your project is in TypeScript or JavaScript, enum-plus works perfectly fine. Both of them can benefit from type safety and intelligent code completion. You don't have to install TypeScript dependencies in your project, since modern code editors like VSCode have built-in support for TypeScript.

Do I have to upgrade TypeScript to version 5.0+?

Not necessarily. The purpose of upgrading to TypeScript 5.0 is to provide a better development experience. If you choose not to upgrade, it will still work fine with just a little extra effort.

const WeekEnum = Enum({
  Sunday: 0,
  Monday: 1,
} as const);

As you can see, in earlier versions of TypeScript, you may need to use the as const type assertion. as const allows the enum values to remain their original literal values instead of being converted to number or string types. Meanwhile, the enum.valueType will remain as 0 | 1 instead of becoming number. This makes TypeScript's type checking more accurate and enhances code safety. Additionally, please check your tsconfig.json file to ensure that the moduleResolution option is set to node or node10, which prevents the type declaration files of enum-plus from being automatically switched to the version of 5.0+.

If you are using JavaScript, you can leverage jsdoc to help the editor accurately recognize types.

/** @type {{ Sunday: 0; Monday: 1 }} */
const weekInit = { Sunday: 0, Monday: 1 };

const WeekEnum = Enum(weekInit);

I saw in the release notes that you made Jest and Playwright share the same set of test code, which is interesting. Can you introduce how to achieve this?

Yes, actually it wasn't easy from the beginning. The working principles of Jest and Playwright are quite different. Jest runs in a Node.js environment, while Playwright runs in a browser environment and then returns to the Node.js environment to execute assertions. To make them share a set of test code, we did the following:

  1. Environment Adaptation: We wrote an adaptation layer to handle the differences between the two testing frameworks.
  2. Abstract Testing Logic: We abstracted the testing logic into some independent modules, so that these test suites can be reused in different testing frameworks.
  3. Enhanced Serialization Mechanism: E2E tests require running in a browser environment and then passing the running results to the Node.js environment for assertions. To achieve this, we developed an enhanced serialization library. Since the enum-plus enums internally use types like class, function, Symbol, Date, and RegExp, built-in functions rewritten like Symbol.toStringTag and Symbol.hasInstance, and even including Getter/Setter, which are not serializable by JSON.stringify. We implemented support for these complex features through jsoneo. So complex objects can cross different environments through serialization/deserialization while retaining all dynamic behaviors. The transferred object remains "alive", just like the original object has not been serialized.

Based on these efforts, we successfully share a set of test code between Jest and Playwright. It significantly improves the development efficiency of unit tests and reduces maintenance costs. You don't have to maintain two sets of test code. In the future, we will also consider separating the first part into an open-source project. If you are developing a "Universal JavaScript" project, you might also try this approach to share test code.

I have a great idea and would like to contribute to this project. What should I do?

We are very glad to hear that! We sincerely welcome contributions from the community. Here are some guidelines to help you get started:

Thanks to the high flexibility of Plugin System, it is quite easy to extend new features for enum-plus. Depending on the generality and dependencies of the feature, you can choose one of the following three ways to contribute:

  1. Core Library - The new feature is applicable to everyone and does not introduce external dependencies. You can contribute directly to the core library. Please refer to the CONTRIBUTING guide.
  2. Official Plugin Library - The new feature needs to depend on an open framework or library, and many people are already using this framework or library. You can contribute to the official plugin library. Please refer to the Plugin Development Guide.
  3. Custom Plugin - The new feature needs to depend on a private external dependency, or the field is relatively niche. It is recommended that you publish an npm package yourself and share your plugin link in the Awesome Plugins section, which can still be shared with everyone.

Security

If you find a security issue, please follow the Security Policy to report it responsibly.

Support & Star

If you find this project useful, please consider giving it a STAR ⭐️ on GitHub. It helps more people discover the project and encourages us to continue development.