npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@polarityio/vite-plugin-icl

v1.0.1

Published

Vite plugin for the Polarity integration framework – transforms web component names into unique names.

Readme

@polarityio/vite-plugin-icl

CI codecov

A Vite plugin for the Polarity integration framework that automatically transforms web component names into globally unique, versioned names at build time — and handles component discovery, registration, and bundling automatically.

Write clean, readable component names in your source:

<key-value .label=${"Host"} .value=${host}></key-value>

They are transformed into collision-proof names during the build:

<px-int-3f8kzq2m1v-icl-key-value-v1-0-0 .label=${"Host"} .value=${host}></px-int-3f8kzq2m1v-icl-key-value-v1-0-0>

Table of Contents


Requirements

  • Node.js ≥ 24
  • Vite ≥ 5

Installation

npm install --save-dev @polarityio/vite-plugin-icl

Quick Start

Point build.lib.entry at VIRTUAL_COMPONENTS_ID and tell the plugin where your component files live. Everything else is automatic.

// vite.config.ts
import { defineConfig } from 'vite';
import { resolve } from 'node:path';
import { transformComponentNames, VIRTUAL_COMPONENTS_ID } from '@polarityio/vite-plugin-icl';

export default defineConfig({
  plugins: [
    transformComponentNames({
      componentsDir: resolve(__dirname, 'src/web-components'),
    }),
  ],
  build: {
    lib: {
      entry: VIRTUAL_COMPONENTS_ID,
      formats: ['es'],
      fileName: () => 'components.js',
    },
  },
});

That's it. The plugin will:

  1. Scan src/web-components for every .ts file
  2. Derive a unique tag name for each component from its filename
  3. Verify each file exports the expected class
  4. Rewrite tag names in your templates at build time
  5. Inject customElements.define(...) into each component file automatically
  6. Bundle everything into a single output file

Adding a Component

Create a .ts file anywhere inside componentsDir. The filename becomes the component's tag name:

| File | Tag name | |---|---| | key-value.ts | <key-value> | | save-modal.ts | <save-modal> | | modals/confirm.ts | <modals--confirm> |

Each file must export a class named in PascalCase with a Component suffix. The build will fail with a clear error if the class is missing or misnamed.

// src/web-components/key-value.ts
import { LitElement, html } from 'lit';
import { property } from 'lit/decorators.js';

export class KeyValueComponent extends LitElement {
  @property() label = '';
  @property() value = '';

  render() {
    return html`
      <dt>${this.label}</dt>
      <dd>${this.value}</dd>
    `;
  }
}

Naming rules: filenames must start with a lowercase letter and contain only lowercase letters, digits, hyphens, periods, or underscores. Names reserved by the HTML spec (annotation-xml, color-profile, etc.) are not allowed.

Nested components

Files in subdirectories are supported. The directory path is included in the tag name, with path separators replaced by -- (double-hyphen):

src/web-components/
  key-value.ts          →  <key-value>           →  KeyValueComponent
  modals/
    save-modal.ts       →  <modals--save-modal>   →  ModalsSaveModalComponent

Exporting Additional Files

If your library also exports utilities, types, or constants alongside your components, keep a hand-written index.ts for those exports and pass both entries to Vite. Rollup produces one output file per entry.

// vite.config.ts
import { defineConfig } from 'vite';
import { resolve } from 'node:path';
import { transformComponentNames, VIRTUAL_COMPONENTS_ID } from '@polarityio/vite-plugin-icl';

export default defineConfig({
  plugins: [
    transformComponentNames({
      componentsDir: resolve(__dirname, 'src/web-components'),
      additionalEntry: resolve(__dirname, 'src/index.ts'),
    }),
  ],
  build: {
    lib: {
      entry: [VIRTUAL_COMPONENTS_ID, resolve(__dirname, 'src/index.ts')],
      formats: ['es'],
      fileName: (_format, entryName) => {
        if (entryName === 'virtual-icl-components') return 'components.js';
        return 'index.js';
      },
    },
  },
});

This produces:

dist/
  components.js   ← auto-discovered web components
  index.js        ← your custom exports

src/index.ts can export anything — the plugin does not transform it:

// src/index.ts
export { version } from './version.js';
export type { MyConfig } from './types.js';

When using two entry points, fileName must return a distinct name for each entry to prevent one file from overwriting the other.


Opting Out of Auto-Import

By default the plugin serves the VIRTUAL_COMPONENTS_ID virtual entry that automatically pulls every component into the bundle. Set autoImport: false to disable this and manage imports yourself via a hand-written index.ts.

transformComponentNames({
  componentsDir: resolve(__dirname, 'src/web-components'),
  autoImport: false,
})

With autoImport: false:

  • VIRTUAL_COMPONENTS_ID is no longer available as a build entry
  • Your vite.config.ts entry should point at your own index.ts
  • Tag rewriting and customElements.define(...) injection still happen automatically for any component file that is imported

Note: With autoImport: false, any component file that is not reachable from your entry point will be silently absent from the bundle.


Acronym Configuration

The plugin reads the acronym field from config/config.json in your project root to include your integration's identifier in the generated component names.

{
  "acronym": "echo-wc"
}

This produces names like:

px-int-3f8kzq2m1v-echo-wc-key-value-v1-0-0

The acronym is always lowercased regardless of how it is written in the config file. If config/config.json does not exist or the acronym key is absent, it defaults to icl.


System Components

Some components have pre-assigned unique names supplied by the Polarity framework rather than generated by this plugin. These are declared in the components array in config/config.json:

{
  "acronym": "echo-wc",
  "components": [
    {
      "type": "summary",
      "element": "px-int-9b69kxiww6yoxs74n4auduspb-echo-wc-summary-v5-0-0"
    },
    {
      "type": "details",
      "element": "px-int-9b69kxiww6yoxs74n4auduspb-echo-wc-details-v5-0-0"
    }
  ]
}

When the plugin discovers a file whose derived name matches a type in this list, it uses the corresponding element value as the unique name instead of generating one. Everything else — class export verification, tag rewriting, and customElements.define(...) injection — works identically.


Library Component Aliases

When integration-component-library is installed in your project, the plugin can automatically rewrite its component tags into their resolved versioned names at build time, and inject the corresponding import and customElements.define(...) calls. Register library components via the libraryComponents option:

transformComponentNames({
  componentsDir: resolve(__dirname, 'src/web-components'),
  libraryComponents: {
    'data-grid': { className: 'DataGrid' },
  },
})

| Write this | Transforms to | |---|---| | <data-grid> | <px-lib-data-grid-v1-0-0> | | </data-grid> | </px-lib-data-grid-v1-0-0> |

The resolved name is computed from the library's package.json version using the formula px-lib-{name}-v{version} (with dots replaced by hyphens). No library code is executed at build time.

If you prefer to handle library components yourself — for example by using staticHtml / unsafeStatic with the exported name variable, or by referencing the long-form tag name directly — set rewriteLibraryComponents: false:

transformComponentNames({
  componentsDir: resolve(__dirname, 'src/web-components'),
  rewriteLibraryComponents: false,
})

If integration-component-library is not installed, library component rewriting is skipped automatically with a console warning.


Custom Library Components

If integration-component-library ships components, you can register them with the libraryComponents option. Each key is the short tag name (kebab-case) and the value specifies the named class export from the library:

// vite.config.ts
transformComponentNames({
  componentsDir: resolve(__dirname, 'src/web-components'),
  libraryComponents: {
    'data-grid': { className: 'DataGrid' },
    'status-badge': { className: 'StatusBadge' },
  },
})

These entries are used by the plugin to rewrite tags, inject imports, and register them via customElements.define(...).

| Write this | Transforms to | |---|---| | <data-grid> | <px-lib-data-grid-v1-0-0> | | <status-badge> | <px-lib-status-badge-v1-0-0> |

Note: libraryComponents is ignored when rewriteLibraryComponents is set to false.


Component Registries

Component registries provide a generic, convention-free way for any component library to declare its tag-name mappings. The plugin reads a JSON file exported by the library and automatically handles tag rewriting, imports, and customElements.define(...) injection — without hard-coding any naming conventions.

Using a Registry

Point the componentRegistries option at one or more registry JSON files. Module specifiers are resolved via require.resolve() (found in node_modules automatically):

// vite.config.ts
transformComponentNames({
  componentsDir: resolve(__dirname, 'src/web-components'),
  componentRegistries: [
    'integration-component-library/component-registry.json',
  ],
})

Multiple registries are supported — for example, if you consume components from several libraries:

componentRegistries: [
  'integration-component-library/component-registry.json',
  '@acme/ui-library/component-registry.json',
],

With a registry loaded, you can use simple tag names in your templates:

| Write this | Transforms to | |---|---| | <pi-button> | <pi-button-v1-0-0> | | <pi-key-value> | <pi-key-value-v1-0-0> |

The plugin also injects the corresponding import and customElements.define(...) calls so the components are bundled and registered automatically.

Publishing a Registry

To make your component library compatible with componentRegistries, generate and export a JSON file in this format:

{
  "pi-button": {
    "element": "pi-button-v1-0-0",
    "className": "PiButton",
    "package": "@polarity/button"
  },
  "pi-checkbox": {
    "element": "pi-checkbox-v1-0-0",
    "className": "PiCheckbox",
    "package": "@polarity/checkbox"
  }
}

Fields:

| Field | Type | Description | |---|---|---| | key | string | The short tag name developers write in templates (e.g. pi-button) | | element | string | The globally unique element name to register (e.g. pi-button-v1-0-0) | | className | string | The named class export from the package (e.g. PiButton) | | package | string | The npm package to import the class from (e.g. @polarity/button) |

Steps to publish:

  1. Generate the registry at build time — write a script that scans your component packages, reads each package.json version, and outputs the JSON mapping. This avoids manual maintenance.

  2. Export the file in package.json — add an exports entry so the plugin can resolve it:

    {
      "exports": {
        "./component-registry.json": "./dist/component-registry.json"
      }
    }
  3. Include it in the published package — ensure dist/component-registry.json is in your files array or not excluded by .npmignore.


Plugin Options

componentsDir (required)

| Type | |---| | string |

The absolute path to the directory containing your web component source files. The plugin scans this directory recursively to discover components and uses it as a filter — only .ts files inside this directory are processed.

componentsDir: resolve(__dirname, 'src/web-components')

autoImport

| Type | Default | |---|---| | boolean | true |

When true, the VIRTUAL_COMPONENTS_ID virtual entry is available and automatically imports every discovered component into the bundle. Set to false to manage your own entry point and imports.

See Opting Out of Auto-Import for details.


additionalEntry

| Type | Default | |---|---| | string | — |

The absolute path to a hand-written entry file for exports beyond the auto-discovered components. The build will fail immediately with a clear error if this file does not exist.

See Exporting Additional Files for a full example.


rewriteLibraryComponents

| Type | Default | |---|---| | boolean | true |

When true, the plugin rewrites integration-component-library component tags (registered via libraryComponents) into their resolved versioned names and injects import / customElements.define(...) calls automatically.

Set to false to handle library components yourself.

See Library Component Aliases for details.


libraryComponents

| Type | Default | |---|---| | Record<string, { className: string }> | — |

Additional library component definitions to register. Each key is the short tag name (kebab-case) and the value specifies the named export from integration-component-library.

User-provided entries are resolved at build time. If a key conflicts with a built-in definition, the user-provided value takes precedence and a build warning is emitted.

This option is ignored when rewriteLibraryComponents is set to false.

See Custom Library Components for a full example.


componentRegistries

| Type | Default | |---|---| | string[] | — |

An array of component registry module specifiers or absolute file paths. Each entry points to a JSON file that maps short tag names to their versioned element names, class names, and source packages.

Module specifiers are resolved via require.resolve() (found in node_modules automatically). Absolute paths are used as-is.

This option is ignored when rewriteLibraryComponents is set to false.

See Component Registries for format details and a full example.


Contributing

See DEVELOPMENT.md for architecture details, how to run tests, and how to contribute.