Aligned with latest utils and types packages post submodule dismantle

This commit is contained in:
2025-11-11 17:42:50 +05:30
commit e17a8f4ef8
19 changed files with 5557 additions and 0 deletions

33
.gitignore vendored Normal file
View File

@@ -0,0 +1,33 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# Dependencies
node_modules
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
# Swap the comments on the following lines if you don't wish to use zero-installs
# Documentation here: https://yarnpkg.com/features/zero-installs
!.yarn/cache
#.pnp.*
# Testing
coverage
# Production
build
# Miscellaneous
*.local
.DS_Store
helper

6
Jenkinsfile vendored Normal file
View File

@@ -0,0 +1,6 @@
@Library('jenkins-shared') _
kanikoPipeline(
repoName: 'Icon',
branch: env.BRANCH_NAME ?: 'main',
isNpmLib: true
)

37
README.md Normal file
View File

@@ -0,0 +1,37 @@
# Armco Icon Component
## Overview
This package provides a flexible, theme-aware React Icon component supporting SVG, base64, image URLs, and identifier-based icon loading. It includes dynamic styling, event handling, and registry-based caching for efficient icon management.
## Source Files
- **Icon.tsx**: Main React component. Handles icon loading, parsing, registry caching, dynamic styling, and event handlers. Supports SVG, base64, raw image, and identifier/URL sources. Uses `withTheme` HOC for theme context.
- **enums.ts**: Enumerations for icon source types, valid image MIME types, tile types, and popover slots.
- **helper.ts**: Utility functions for:
- Inferring icon type from source
- Parsing SVG strings and base64
- Creating React elements from SVG DOM
- Applying dynamic styles (fill, stroke, size, classes)
- Color helpers for theme/toggle/hover states
- **types.ts**: TypeScript interfaces for icon props, state, registry, attributes, events, and API responses.
- **Icon.component.scss**: SCSS styles for icon presentation and hover effects.
- **vite-env.d.ts**: Vite client types.
## Key Features
- **Dynamic Source Handling**: Accepts icon as string, object, or IconSource; supports SVG, base64, image URLs, and identifier-based API fetch.
- **Registry Caching**: Prevents duplicate network requests for icons; caches both promises and loaded icons.
- **Theme & State Styling**: Applies fill/stroke/colors based on theme, hover, and toggle state; supports per-path coloring.
- **Event Handling**: Supports onClick, onMouseEnter, onMouseLeave via props.
- **SVG to React Conversion**: Converts SVG DOM to ReactNode for full React event support.
## Usage
```tsx
import Icon from "@armco/icon"
<Icon icon="md.MdEdit" attributes={{ colors: { fillColor: "#333" } }} />
```
## Development
- Written in TypeScript, React class component.
- Utilities and types are modularized for clarity and reuse.
- SCSS for styling.

36
build-tools/build.sh Executable file
View File

@@ -0,0 +1,36 @@
#!/bin/bash
# Get the directory of the current script
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Default values
DEV_FLAG=""
# Parse arguments
for arg in "$@"
do
case $arg in
--dev)
DEV_FLAG="--dev"
shift # Remove --dev from processing
;;
esac
done
echo "[BUILD:SH] Dev flag is: $DEV_FLAG"
echo "[BUILD:SH] Removing build if exists"
rm -rf build
echo "[BUILD:SH] Checking TS Types"
npx tsc
echo "[BUILD:SH] Initiating build..."
# Conditionally use vite-dev.config.ts if --dev flag is present
if [ "$DEV_FLAG" == "--dev" ]; then
vite build --config vite-dev.config.ts
else
vite build
fi
echo "[BUILD:SH] Running post processor scripts..."
# Run Post processors: Update style imports in .js files, create component modules
node "$SCRIPT_DIR/post-processor.js" build/cjs $DEV_FLAG
node "$SCRIPT_DIR/post-processor.js" build/es $DEV_FLAG

View File

@@ -0,0 +1,33 @@
import { promises as fs } from "fs"
import { dirname, resolve } from "path"
import { fileURLToPath } from "url"
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
const exclusions = ["Icon.js", "helper2.js"]
async function generateModule(fileName, isDev) {
if (!exclusions.includes(fileName) && fileName.endsWith(".js")) {
const dir = fileName.slice(0, -3)
const name = `@armco/icon/${dir}`
const packageJsonContent = {
name,
main: `../${isDev ? "build/" : ""}cjs/${dir}.js`,
module: `../${isDev ? "build/" : ""}es/${dir}.js`,
types: `../${isDev ? "build/" : ""}types/${dir}.d.ts`,
}
const dirPath = resolve(__dirname, `../${isDev ? "" : "build/"}${dir}`)
try {
await fs.mkdir(dirPath, { recursive: true })
await fs.writeFile(
resolve(dirPath, "package.json"),
JSON.stringify(packageJsonContent, null, 2),
)
} catch (error) {
console.error(`Error processing directory ${dirPath}:`, error)
}
}
}
export default generateModule

View File

@@ -0,0 +1,24 @@
import { readdir } from "fs/promises"
import generateModule from "./generate-module.js"
async function postProcessor(dir, isDev) {
try {
const files = await readdir(dir)
await Promise.all(
files.map(async (file) => {
await generateModule(file, isDev)
}),
)
} catch (error) {
console.error(`Error processing directory ${dir}:`, error)
}
}
const targetDir = process.argv[2]
if (targetDir) {
postProcessor(targetDir, process.argv.includes("--dev"))
} else {
console.error("Please provide the build directory to run post processor on.")
process.exit(1)
}

4637
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

67
package.json Normal file
View File

@@ -0,0 +1,67 @@
{
"name": "@armco/icon",
"version": "0.0.10",
"type": "module",
"main": "build/cjs/Icon.js",
"module": "build/es/Icon.js",
"types": "build/types/Icon.d.ts",
"scripts": {
"build": "./build-tools/build.sh",
"build:sm": "./build-tools/build.sh --dev",
"format": "prettier --write .",
"lint": "eslint .",
"publish:sh": "./publish.sh",
"publish:local": "./publish-local.sh"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
],
"plugins": [
"prettier"
],
"rules": {
"prettier/prettier": "error",
"react/jsx-no-target-blank": "off"
}
},
"peerDependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"prettier": "prettier-config-nick",
"repository": {
"type": "git",
"url": "git+https://gitea.armco.dev/ReStruct-Corporate-Advantage/icon.git"
},
"keywords": [
"components",
"atomic",
"building-blocks",
"foundation"
],
"files": [
"build"
],
"license": "ISC",
"bugs": {
"url": "https://gitea.armco.dev/ReStruct-Corporate-Advantage/icon/issues"
},
"homepage": "https://gitea.armco.dev/ReStruct-Corporate-Advantage/icon#readme",
"devDependencies": {
"@armco/types": "^0.0.22",
"@types/node": "^24.10.0",
"@types/react": "^19.2.2",
"@vitejs/plugin-react": "^5.1.0",
"react": "^18.3.1",
"sass-embedded": "^1.93.3",
"vite-plugin-css-injected-by-js": "^3.5.2",
"vite-plugin-dts": "^4.5.4",
"vite-plugin-externalize-deps": "^0.10.0",
"vitest": "^4.0.8"
},
"dependencies": {
"@armco/utils": "^0.0.31"
}
}

16
publish-local.sh Executable file
View File

@@ -0,0 +1,16 @@
#!/bin/sh
semver=${1:-patch}
set -e
npm run build
cp package.json build/
sed -i '' -E 's/"build"/"*"/' build/package.json
sed -i '' 's#"build/cjs/Icon.js"#"cjs/Icon.js"#' build/package.json
sed -i '' 's#"build/es/Icon.js"#"es/Icon.js"#' build/package.json
sed -i '' 's#"build/types/Icon.d.ts"#"types/Icon.d.ts"#' build/package.json
cd build
npm pack --pack-destination ~/__Projects__/Common

16
publish.sh Executable file
View File

@@ -0,0 +1,16 @@
#!/bin/sh
semver=${1:-patch}
set -e
npm --no-git-tag-version version ${semver}
npm run build
cp package.json build/
sed -i '' -E 's/"build"/"*"/' build/package.json
sed -i '' 's#"build/cjs/Icon.js"#"cjs/Icon.js"#' build/package.json
sed -i '' 's#"build/es/Icon.js"#"es/Icon.js"#' build/package.json
sed -i '' 's#"build/types/Icon.d.ts"#"types/Icon.d.ts"#' build/package.json
cd build
npm publish --access public --loglevel verbose

7
src/Icon.component.scss Executable file
View File

@@ -0,0 +1,7 @@
.ar-Icon {
transition: all 0.3s;
&:hover.hover-shadow {
box-shadow: 0px 4px 8px var(--ar-shadow);
}
}

199
src/Icon.tsx Normal file
View File

@@ -0,0 +1,199 @@
import { cloneElement, Component, ReactElement } from "react"
import {
ArIconSourceTypes,
ArValidImageMimeTypes,
} from "./enums"
import {
IconRegistry,
IconSource,
IconProps,
IconState
} from "./types"
import { get, getStatic, retry } from "@armco/utils/network"
import { withTheme } from "@armco/utils/HOC"
import {
applyStyles,
createElementFromSvg,
inferIconType,
parseSvgB64String,
parseSvgString,
placeholder,
} from "./helper"
import "./Icon.component.scss"
const iconRegistry: IconRegistry = {}
class Icon extends Component<IconProps, IconState> {
constructor(props: IconProps) {
super(props)
this.state = {
hovered: false,
toggled: props.toggled || false,
milk: placeholder,
type: ArIconSourceTypes.svgString,
}
this.parseIconDescriptor = this.parseIconDescriptor.bind(this)
this.handleIconResponse = this.handleIconResponse.bind(this)
}
componentDidMount() {
this.props.icon && this.parseIconDescriptor(this.props.icon)
}
componentDidUpdate(prevProps: IconProps, prevState: IconState) {
if (this.props.icon && this.props.icon !== prevProps.icon) {
// Fetch icon if icon changed in props
this.parseIconDescriptor(this.props.icon)
} else if (
JSON.stringify(prevProps.attributes) !==
JSON.stringify(this.props.attributes) ||
this.state.hovered !== prevState.hovered ||
this.state.toggled !== prevState.toggled ||
this.state.milk !== prevState.milk
) {
// Apply styles, if hovered, toggled or attributes changed
this.state.milk &&
this.setState({
shake: applyStyles.call(
this,
this.state.milk,
this.props,
this.state,
),
})
}
}
handleIconResponse(res: any, source: string, type: ArIconSourceTypes) {
const contentType = res.headers.get("content-type")
let milk
if (
type === ArIconSourceTypes.identifier ||
contentType?.includes(ArValidImageMimeTypes.SVG)
) {
milk = parseSvgString(res.body)
} else if (
contentType &&
Object.values(ArValidImageMimeTypes).findIndex((imageMimeType) =>
contentType.includes(imageMimeType),
) > -1
) {
milk = (
<img
src={source as any}
alt="ar-icon"
className={`ar-Icon ${this.props.attributes?.classes}${this.props.hoverShadow ? " hover-shadow p-1" : ""
}`}
style={this.props.attributes?.styles}
/>
)
} else {
console.error("Unsupported content type.")
milk = placeholder as SVGSVGElement
}
milk && this.setState({ milk, type })
iconRegistry[source].icon = milk
}
parseIconDescriptor = (icon: IconSource | string | object): void => {
const isIconSourceType = typeof icon === "object" && "source" in icon
const iconTypeExists = isIconSourceType && !!ArIconSourceTypes[icon.type]
let source = isIconSourceType ? icon.source : icon
const type = iconTypeExists ? icon.type : inferIconType(source)
let milk
if (
type === ArIconSourceTypes.identifier ||
type === ArIconSourceTypes.URL
) {
source = source as string
const url =
type === ArIconSourceTypes.URL
? source
: "/icon/" + source.replace(/\./g, "/")
const fetcher = () =>
(type === ArIconSourceTypes.URL ? get : getStatic)(url)
// Checking for existing promise is done to prevent multiple concurrent API calls for the same icon.
// Check if API has already been called (iconRegistry[source].promise is placed in registry as soon as call is made)
if (iconRegistry[source]) {
if (iconRegistry[source].icon) {
// If promise exists in registry as well as corresponding icon too, just set the icon to state
this.setState({
milk: iconRegistry[source].icon ?? placeholder,
type,
})
} else {
// If just promise exists resolve it
iconRegistry[source].promise?.then((res) =>
this.handleIconResponse(res, source as string, type),
)
}
} else {
// If neither of promise and icon exist, make a fresh API call
const promise = retry(fetcher).then((res) => {
this.handleIconResponse(res, source as string, type)
return res
})
iconRegistry[source] = { promise }
}
} else {
switch (type) {
case ArIconSourceTypes.b64:
milk = parseSvgB64String(source as string)
break
case ArIconSourceTypes.raw:
milk = (
<img
src={source as any}
alt="ar-icon"
className={`ar-Icon ${this.props.attributes?.classes}${this.props.hoverShadow ? " hover-shadow p-1" : ""
}`}
style={this.props.attributes?.styles}
/>
)
break
case ArIconSourceTypes.svgString:
milk = parseSvgString(source as string)
break
default:
console.error("Unsupported icon type.")
milk = placeholder as SVGSVGElement
}
milk && this.setState({ milk, type: type as ArIconSourceTypes })
}
}
handleMouseEnter = () => {
this.setState({ hovered: true })
this.props.events?.onMouseEnter?.()
}
handleMouseLeave = () => {
this.setState({ hovered: false })
this.props.events?.onMouseLeave?.()
}
render() {
const { shake, type } = this.state
if (!shake) {
return null
} else if (type === ArIconSourceTypes.raw) {
return shake as ReactElement
} else {
return cloneElement(
createElementFromSvg(shake as SVGSVGElement) as ReactElement,
{
onMouseEnter: this.handleMouseEnter,
onMouseLeave: this.handleMouseLeave,
onClick: this.props.events?.onClick,
onClickCapture: () => this.setState({ toggled: !this.state.toggled }),
} as React.HTMLAttributes<SVGElement>,
)
}
}
}
export default withTheme<IconProps>(Icon)

29
src/enums.ts Normal file
View File

@@ -0,0 +1,29 @@
export enum ArIconSourceTypes {
URL = "URL",
identifier = "identifier",
svgString = "svgString",
b64 = "b64",
raw = "raw",
}
export enum ArIconTileTypes {
COMFY = "comfy",
COMPACT = "compact",
LIST = "list",
}
export enum ArValidImageMimeTypes {
JPEG = "image/jpeg",
PNG = "image/png",
GIF = "image/gif",
WEBP = "image/webp",
SVG = "image/svg+xml",
TIFF = "image/tiff",
BMP = "image/bmp",
ICON = "image/x-icon",
}
export enum ArPopoverSlots {
POPOVER = "popover",
ANCHOR = "anchor",
}

218
src/helper.ts Normal file
View File

@@ -0,0 +1,218 @@
import {
createElement as createReactElement,
CSSProperties,
ReactElement,
} from "react"
import { ArThemes } from "@armco/utils"
import {
ArIconSourceTypes,
ArValidImageMimeTypes,
} from "./enums"
import {
IconProps,
IconState
} from "./types"
const parser = new DOMParser()
export const placeholder = parser
.parseFromString(
`<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg">
<rect width="20" height="20" x="2" y="2" fill="none" stroke="#000" stroke-width="2"rx="2"></rect>
</svg>`,
ArValidImageMimeTypes.SVG,
)
.querySelector("svg") as SVGSVGElement
const iconIdRegex = /^[a-zA-Z0-9]{2,3}[.\/][a-zA-Z0-9]+$/
export const inferIconType = (source: string | object): ArIconSourceTypes => {
// At this point, icon is not IconSource, it's string or raw
if (typeof source === "object") {
return ArIconSourceTypes.raw
}
if (source.startsWith("data:image/svg+xml;base64,")) {
return ArIconSourceTypes.b64
} else if (source.startsWith("<svg")) {
return ArIconSourceTypes.svgString
} else if (source.startsWith("http://") || source.startsWith("https://")) {
return ArIconSourceTypes.URL
} else if (iconIdRegex.test(source)) {
return ArIconSourceTypes.identifier
} else {
return ArIconSourceTypes.identifier
}
}
export const parseSvgString = (svgString: string): SVGSVGElement => {
try {
const doc = parser.parseFromString(svgString, ArValidImageMimeTypes.SVG)
const svg = doc.querySelector("svg")
return svg === null ? placeholder : svg
} catch (error) {
console.warn("Error parsing SVG string:", error)
return placeholder
}
}
export const parseSvgB64String = (svgB64String: string): SVGSVGElement => {
try {
const svgString = atob(svgB64String)
return parseSvgString(svgString)
} catch (error) {
console.error("Error decoding base64 SVG string:", error)
return placeholder
}
}
export const createElementFromSvg = (juice: SVGSVGElement): React.ReactNode => {
const createElement = (node: Element): React.ReactNode => {
const children = Array.from(node.children).map(createElement)
const props = Array.from(node.attributes).reduce((acc, attr) => {
if (attr.name === "style") {
// Parse the style attribute string into an object as React expects style to be object and not
// string on components
const styleObject = attr.value
.split(";")
.reduce((styleAcc, styleAttr) => {
const [key, value] = styleAttr.split(":").map((str) => str.trim())
if (key && value) {
styleAcc[key] = value
}
return styleAcc
}, {} as Record<string, string>)
acc["style"] = styleObject
} else {
acc[attr.name] = attr.value
}
return acc
}, {} as Record<string, any>)
return createReactElement(node.tagName, props, ...children)
}
return createElement(juice)
}
export const getStrokeColor = (
props: IconProps,
state?: IconState,
): string | undefined => {
const { attributes, theme } = props
const { colors } = attributes || {}
const {
strokeColor,
toggleStrokeColor,
hoverStrokeColor,
darkStrokeColor,
darkToggleStrokeColor,
darkHoverStrokeColor,
} = colors || {}
const { hovered, toggled } = state || {}
if (theme === ArThemes.DARK1) {
if (hovered && darkHoverStrokeColor)
return darkHoverStrokeColor || hoverStrokeColor
if (toggled && darkToggleStrokeColor)
return darkToggleStrokeColor || toggleStrokeColor
return darkStrokeColor || strokeColor
} else {
if (hovered && hoverStrokeColor) return hoverStrokeColor
if (toggled && toggleStrokeColor) return toggleStrokeColor
return strokeColor
}
}
export const getFillColor = (
props: IconProps,
state?: IconState,
): string | undefined => {
const { attributes, theme } = props
const { colors } = attributes || {}
const {
fillColor,
toggleFillColor,
hoverFillColor,
darkFillColor,
darkToggleFillColor,
darkHoverFillColor,
} = colors || {}
const { hovered, toggled } = state || {}
if (theme === ArThemes.DARK1) {
if (hovered && darkHoverFillColor)
return darkHoverFillColor || hoverFillColor
if (toggled && darkToggleFillColor)
return darkToggleFillColor || toggleFillColor
return darkFillColor || fillColor
} else {
if (hovered && hoverFillColor) return hoverFillColor
if (toggled && toggleFillColor) return toggleFillColor
return fillColor
}
}
export const applyStyles = (
milk: SVGSVGElement | ReactElement,
props: IconProps,
state?: IconState,
) => {
if (!(milk instanceof SVGSVGElement)) {
return
}
const { attributes, fillPath, hoverShadow } = props
const { strokeWidth, styles } = attributes || {}
const classes =
(attributes?.classes || "") + (hoverShadow ? " hover-shadow p-1" : "")
const fillColor = getFillColor(props, state)
const strokeColor = getStrokeColor(props, state)
const height =
attributes?.height ||
(typeof styles?.height === "number"
? styles?.height + "px"
: styles?.height) ||
attributes?.size ||
"1rem"
const width =
attributes?.width ||
(typeof styles?.width === "number"
? styles?.width + "px"
: styles?.width) ||
attributes?.size ||
"1rem"
const shake = milk.cloneNode(true) as SVGSVGElement
fillColor && shake.setAttribute("fill", fillColor)
strokeColor && shake.setAttribute("stroke", strokeColor)
strokeWidth && shake.setAttribute("stroke-width", strokeWidth)
shake.setAttribute("width", width)
shake.setAttribute("height", height)
shake.setAttribute("class", `ar-Icon${classes ? " " + classes : ""}`)
styles &&
Object.keys(styles).forEach((key) => {
shake.style[key as any] = styles[key as keyof CSSProperties] as string
})
if (fillPath) {
let paths: Array<SVGPathElement> | null = Array.from(
shake.querySelectorAll("path"),
)
paths =
fillPath === true
? paths
: fillPath.length !== undefined
? paths.filter((_, index) => fillPath.includes(index))
: null
paths?.forEach((path) => {
// Override stroke and color of "path" node inside SVG at below line.
strokeColor &&
!!path.getAttribute("stroke") &&
path.setAttribute("stroke", strokeColor)
strokeWidth &&
!!path.getAttribute("stroke-width") &&
path.setAttribute("stroke-width", strokeWidth)
fillColor &&
!!path.getAttribute("fill") &&
path.setAttribute("fill", fillColor)
})
}
return shake
}

82
src/types.ts Normal file
View File

@@ -0,0 +1,82 @@
import { CSSProperties, ReactElement } from "react"
import { PageItem } from "@armco/types"
import { ArThemes } from "@armco/utils"
import { ArIconSourceTypes, ArPopoverSlots } from "./enums"
export interface IconSource {
source: string | object
type: ArIconSourceTypes
}
export interface IconAttributes {
height?: string
width?: string
size?: string
strokeWidth?: string
colors?: {
fillColor?: string
strokeColor?: string
strokeWidth?: string
toggleFillColor?: string
toggleStrokeColor?: string
hoverFillColor?: string
hoverStrokeColor?: string
darkFillColor?: string
darkStrokeColor?: string
darkToggleFillColor?: string
darkToggleStrokeColor?: string
darkHoverFillColor?: string
darkHoverStrokeColor?: string
}
styles?: CSSProperties
classes?: string
}
export interface IconEvents {
onClick?: (e?: MouseEvent) => void
onMouseEnter?: (e?: MouseEvent) => void
onMouseLeave?: (e?: MouseEvent) => void
}
export interface IconProps {
icon?: IconSource | string | object
slot?: ArPopoverSlots
attributes?: IconAttributes
events?: IconEvents
fillPath?: Array<number> | boolean
toggled?: boolean
hoverShadow?: boolean
theme?: ArThemes
}
export interface IconState {
hovered: boolean
toggled: boolean
milk: SVGSVGElement | ReactElement
shake?: SVGSVGElement | ReactElement
type: ArIconSourceTypes
}
export type IconRegistry = {
[key: string]: {
promise?: Promise<any>
icon?: SVGSVGElement | ReactElement
}
}
export interface IconStyles {
fillColor?: string
strokeColor?: string
bgColor?: string
strokeWidth?: string
}
export interface IconResponse extends PageItem {
name: string
group: string
svg: string
icon: string
tags?: Array<string>
description?: string
message?: string
}

1
src/vite-env.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
/// <reference types="vite/client" />

27
tsconfig.json Normal file
View File

@@ -0,0 +1,27 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"outDir": "build",
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": [
"src"
]
}

45
vite-dev.config.ts Normal file
View File

@@ -0,0 +1,45 @@
import { resolve } from "path"
import { defineConfig } from "vitest/config"
import react from "@vitejs/plugin-react"
import dts from "vite-plugin-dts"
import cssInjectedByJsPlugin from "vite-plugin-css-injected-by-js"
import { externalizeDeps } from "vite-plugin-externalize-deps"
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
react(),
dts({ outDir: "build/types" }),
cssInjectedByJsPlugin({
jsAssetsFilterFunction: (chunk) => chunk.fileName.includes("Icon"),
}),
externalizeDeps(),
],
build: {
outDir: "build",
lib: {
entry: [
resolve(__dirname, "src/helper.ts"),
resolve(__dirname, "src/Icon.tsx"),
],
},
sourcemap: true,
rollupOptions: {
treeshake: true,
output: [
{
format: "es",
dir: "build/es",
entryFileNames: "[name].js",
chunkFileNames: "[name].js",
},
{
format: "cjs",
dir: "build/cjs",
entryFileNames: "[name].js",
chunkFileNames: "[name].js",
},
],
},
},
})

44
vite.config.ts Normal file
View File

@@ -0,0 +1,44 @@
import { resolve } from "path"
import { defineConfig } from "vitest/config"
import react from "@vitejs/plugin-react"
import dts from "vite-plugin-dts"
import cssInjectedByJsPlugin from "vite-plugin-css-injected-by-js"
import { externalizeDeps } from "vite-plugin-externalize-deps"
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
react(),
dts({ outDir: "build/types" }),
cssInjectedByJsPlugin({
jsAssetsFilterFunction: (chunk) => chunk.fileName.includes("Icon"),
}),
externalizeDeps(),
],
build: {
outDir: "build",
lib: {
entry: [
resolve(__dirname, "src/helper.ts"),
resolve(__dirname, "src/Icon.tsx"),
],
},
rollupOptions: {
treeshake: true,
output: [
{
format: "es",
dir: "build/es",
entryFileNames: "[name].js",
chunkFileNames: "[name].js",
},
{
format: "cjs",
dir: "build/cjs",
entryFileNames: "[name].js",
chunkFileNames: "[name].js",
},
],
},
},
})