diff --git a/.changeset/README.md b/.changeset/README.md new file mode 100644 index 0000000..e5b6d8d --- /dev/null +++ b/.changeset/README.md @@ -0,0 +1,8 @@ +# Changesets + +Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works +with multi-package repos, or single-package repos to help you version and publish your code. You can +find the full documentation for it [in our repository](https://github.com/changesets/changesets) + +We have a quick list of common questions to get you started engaging with this project in +[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 0000000..0b95d68 --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json", + "changelog": [ + "@changesets/changelog-github", + { + "repo": "stainless-code/react-memo" + } + ], + "commit": false, + "fixed": [], + "linked": [], + "access": "public", + "baseBranch": "main", + "updateInternalDependencies": "patch", + "ignore": [] +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..ae10a5c --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +# editorconfig.org +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 0000000..d0e9fce --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,30 @@ +/** @type {import("eslint").Linter.Config} */ +module.exports = { + env: { + browser: true, + es2021: true, + node: true, + }, + extends: [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "plugin:react-hooks/recommended", + "plugin:react/jsx-runtime", + "plugin:react/recommended", + "prettier", + ], + parser: "@typescript-eslint/parser", + parserOptions: { + ecmaVersion: "latest", + sourceType: "module", + }, + plugins: ["@typescript-eslint", "react"], + settings: { + react: { + version: "detect", + }, + }, + rules: { + "@typescript-eslint/no-explicit-any": "off", + }, +}; diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..b3092ae --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: [SutuSebastian] diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..9b77ea7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,40 @@ +--- +name: Bug report +about: Create a report to help us improve +title: "" +labels: "" +assignees: "" +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: + +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + +- OS: [e.g. iOS] +- Browser [e.g. chrome, safari] +- Version [e.g. 22] + +**Smartphone (please complete the following information):** + +- Device: [e.g. iPhone6] +- OS: [e.g. iOS8.1] +- Browser [e.g. stock browser, safari] +- Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..2bc5d5f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,19 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: "" +labels: "" +assignees: "" +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml new file mode 100644 index 0000000..0464b8e --- /dev/null +++ b/.github/actions/setup/action.yml @@ -0,0 +1,14 @@ +name: Setup +description: Setup Bun and install packages + +runs: + using: composite + steps: + - name: Setup Bun + uses: oven-sh/setup-bun@v1 + with: + bun-version: latest + + - name: Install packages + shell: bash + run: bun install diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..e53f191 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,85 @@ +name: CI + +on: + pull_request: + branches: + - main + +jobs: + format: + if: github.event.pull_request.head.ref != 'changeset-release/main' + name: ๐Ÿ’… Format + runs-on: ubuntu-latest + steps: + - name: Checkout branch + uses: actions/checkout@v4 + + - name: Setup + uses: ./.github/actions/setup + + - name: Run format + run: bun run format:check + + lint: + if: github.event.pull_request.head.ref != 'changeset-release/main' + name: ๐Ÿ•ต Lint + runs-on: ubuntu-latest + steps: + - name: Checkout branch + uses: actions/checkout@v4 + + - name: Setup + uses: ./.github/actions/setup + + - name: Run lint + run: bun run lint + + typecheck: + if: github.event.pull_request.head.ref != 'changeset-release/main' + name: โœ… Typecheck + runs-on: ubuntu-latest + steps: + - name: Checkout branch + uses: actions/checkout@v4 + + - name: Setup + uses: ./.github/actions/setup + + - name: Run typecheck + run: bun run typecheck + + test: + if: github.event.pull_request.head.ref != 'changeset-release/main' + name: ๐Ÿงช Test + runs-on: ubuntu-latest + steps: + - name: Checkout branch + uses: actions/checkout@v4 + + - name: Setup + uses: ./.github/actions/setup + + - name: Run test + run: bun run test + + build: + if: github.event.pull_request.head.ref != 'changeset-release/main' + name: ๐Ÿงฐ Build + runs-on: ubuntu-latest + steps: + - name: Checkout branch + uses: actions/checkout@v4 + + - name: Setup + uses: ./.github/actions/setup + + - name: Run build + run: bun run build + + status-check: + if: github.event.pull_request.head.ref == 'changeset-release/main' + name: ๐Ÿ“‹ Status check + runs-on: ubuntu-latest + steps: + - name: Report Success + run: echo "Marking all checks as successful for changeset-release/main branch." diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..3443314 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,28 @@ +name: Release + +on: + push: + branches: + - main + +concurrency: ${{ github.workflow }}-${{ github.ref }} + +jobs: + release: + name: Release + runs-on: ubuntu-latest + steps: + - name: Checkout branch + uses: actions/checkout@v4 + + - name: Setup + uses: ./.github/actions/setup + + - name: Create Release Pull Request or Publish to NPM + id: changesets + uses: changesets/action@v1 + with: + publish: bun run release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac9d097 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.DS_Store +dist +node_modules diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..099bae3 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "DavidAnson.vscode-markdownlint", + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "oven.bun-vscode", + "yoavbls.pretty-ts-errors" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..2ed5998 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit" + }, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true, + "typescript.tsdk": "node_modules/typescript/lib" +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..8abb1f9 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,143 @@ +# Contributing to @stainless-code/react-memo + +Thank you for your interest in contributing to **@stainless-code/react-memo**! Your contributions are greatly appreciated, whether it's fixing a bug, adding a feature, improving documentation, or providing feedback. + +## Table of Contents + +- [Contributing to @stainless-code/react-memo](#contributing-to-stainless-codereact-memo) + - [Table of Contents](#table-of-contents) + - [Getting Started](#getting-started) + - [Development Workflow](#development-workflow) + - [Build the project](#build-the-project) + - [Start development mode](#start-development-mode) + - [Scripts Overview](#scripts-overview) + - [Testing](#testing) + - [Code Style](#code-style) + - [Submitting a Pull Request](#submitting-a-pull-request) + - [Thank You!](#thank-you) + +--- + +## Getting Started + +1. Fork the repository on GitHub: + [https://github.com/stainless-code/react-memo](https://github.com/stainless-code/react-memo) + +2. Clone your fork locally: + + ```bash + git clone https://github.com//react-memo.git + cd react-memo + ``` + +3. Install dependencies: + + ```bash + bun install + ``` + + Make sure you have [Bun](https://bun.sh/) installed, or use an alternative package manager (`npm`, `yarn`, or `pnpm`). + +--- + +## Development Workflow + +### Build the project + +Run the build process to compile the library: + +```bash +bun run build +``` + +This will generate the compiled output in the `dist` directory. + +### Start development mode + +To watch for changes and rebuild automatically during development: + +```bash +bun run dev +``` + +--- + +## Scripts Overview + +The following scripts are available in the `package.json` file: + +| Script | Command | Description | +| -------------- | -------------------------- | ----------------------------------------------- | +| `build` | `tsup` | Builds the project. | +| `clean` | `rimraf dist node_modules` | Cleans the output and dependencies. | +| `dev` | `tsup --watch` | Starts a development build watcher. | +| `format` | `prettier . --write` | Formats code using Prettier. | +| `format:check` | `prettier . --check` | Checks code formatting without modifying files. | +| `lint` | `eslint .` | Lints the project for code quality. | +| `lint:fix` | `eslint . --fix` | Fixes linting issues automatically. | +| `test` | `vitest run` | Runs all tests using Vitest. | +| `typecheck` | `tsc` | Verifies TypeScript type definitions. | +| `release` | `changeset publish` | Publishes a new release. | +| `version` | `changeset version` | Prepares a version update with Changesets. | + +--- + +## Testing + +We use [Vitest](https://vitest.dev/) for testing. To run the tests: + +```bash +bun run test +``` + +To run tests in watch mode: + +```bash +bun run test -- --watch +``` + +Please ensure all tests pass before submitting a pull request. + +--- + +## Code Style + +We follow strict coding standards enforced by Prettier and ESLint. + +- Run `bun run lint` to check for linting issues. +- Run `bun run lint:fix` to automatically fix issues. +- Run `bun run format` to format the codebase. + +Before submitting your changes, ensure your code passes linting and formatting checks. + +--- + +## Submitting a Pull Request + +1. Create a feature branch: + + ```bash + git checkout -b my-feature-branch + ``` + +2. Make your changes and commit them: + + ```bash + git add . + git commit -m "feat: add my feature" + ``` + +3. Push your changes to your fork: + + ```bash + git push origin my-feature-branch + ``` + +4. Open a pull request on GitHub to the `main` branch of the upstream repository: + [https://github.com/stainless-code/react-memo/pulls](https://github.com/stainless-code/react-memo/pulls) + +--- + +## Thank You! + +Your contributions are valued and appreciated! If you encounter any issues or need guidance, feel free to open an issue or contact us at [sebastian.sutu@stainless-code.com](mailto:sebastian.sutu@stainless-code.com). diff --git a/README.md b/README.md index ccc72a5..726e5fe 100644 --- a/README.md +++ b/README.md @@ -1 +1,141 @@ -# react-memo \ No newline at end of file +# @stainless-code/react-memo + +A utility library to simplify and enhance memoization for React functional components using `React.memo`. + +## Features + +- Simplify memoization with `React.memo`. +- Fine-grained control over dependency-based re-renders. +- Support for one-time memoization where components never re-render. +- Fully type-safe with TypeScript. + +## Installation + +### npm + +```bash +npm install @stainless-code/react-memo +``` + +### yarn + +```bash +yarn add @stainless-code/react-memo +``` + +### pnpm + +```bash +pnpm add @stainless-code/react-memo +``` + +### bun + +```bash +bun add @stainless-code/react-memo +``` + +## Usage + +Enhance your React components with precise memoization: + +```tsx +import { withMemo, withMemoOnce } from "@stainless-code/react-memo"; +import React from "react"; + +const MyComponent: React.FC<{ value: number; onClick: () => void }> = ({ + value, + onClick, +}) => ; + +// Memoize based on specific dependencies +const MemoizedComponent = withMemo(MyComponent, ["value"]); + +// Memoize the component to never re-render +const MemoizedOnceComponent = withMemoOnce(MyComponent); + +export default function App() { + return ( + <> + console.log("Clicked!")} /> + console.log("Clicked again!")} + /> + + ); +} +``` + +## Typesafety + +### Example: Type Mismatch (Fails) + +If the `dependencyProps` contain keys that don't exist on the component's props, TypeScript will throw an error: + +```tsx +import { withMemo } from "@stainless-code/react-memo"; +import React from "react"; + +const MyComponent: React.FC<{ value: number; onClick: () => void }> = ({ + value, + onClick, +}) => ; + +// โŒ TypeScript Error: "nonExistentProp" does not exist on the props of MyComponent. +const MemoizedComponent = withMemo(MyComponent, ["value", "nonExistentProp"]); + +export default MemoizedComponent; +``` + +### Example: Type Match (Succeeds) + +If the `dependencyProps` accurately reflect the keys of the component's props, TypeScript ensures everything works smoothly: + +```tsx +import { withMemo } from "@stainless-code/react-memo"; +import React from "react"; + +const MyComponent: React.FC<{ value: number; onClick: () => void }> = ({ + value, + onClick, +}) => ; + +// โœ… TypeScript passes: "value" and "onClick" are valid props for MyComponent. +const MemoizedComponent = withMemo(MyComponent, ["value", "onClick"]); + +export default MemoizedComponent; +``` + +## API + +### `withMemo` + +Enhance a functional component with memoization, allowing re-renders only when specific dependencies or props change. + +| Parameter | Type | Default | Description | +| ------------------ | ------------- | ----------- | -------------------------------------------------------------------------------- | +| `Component` | `React.FC` | Required | The React functional component to memoize. | +| `dependencyProps?` | `(keyof T)[]` | `undefined` | An array of prop names to check for changes. If omitted, all props are compared. | + +Returns a `React.MemoExoticComponent` that wraps the input component. + +### `withMemoOnce` + +Memoize a functional component such that it never re-renders, regardless of prop changes. + +| Parameter | Type | Default | Description | +| ----------- | ------------- | -------- | ------------------------------------------ | +| `Component` | `React.FC` | Required | The React functional component to memoize. | + +Returns a `React.MemoExoticComponent` that wraps the input component. + +## Contributing + +Feel free to submit issues or pull requests to improve the library. Every bit of help is appreciated. ๐Ÿ’– + +[Read the contribution guidelines](./CONTRIBUTING.md). + +## License + +[MIT](./LICENSE) diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000..af7d931 Binary files /dev/null and b/bun.lockb differ diff --git a/package.json b/package.json new file mode 100644 index 0000000..b19788b --- /dev/null +++ b/package.json @@ -0,0 +1,90 @@ +{ + "name": "@stainless-code/react-memo", + "version": "1.0.0", + "description": "Elegantly use custom events in React", + "keywords": [ + "memo", + "memoization", + "react-memo", + "react", + "typesafe", + "with-memo" + ], + "homepage": "https://github.com/stainless-code/react-memo#readme", + "bugs": "https://github.com/stainless-code/react-memo/issues", + "repository": { + "type": "git", + "url": "git+https://github.com/stainless-code/react-memo.git" + }, + "license": "MIT", + "author": { + "name": "Sutu Sebastian", + "email": "sebastian.sutu@stainless-code.com", + "url": "https://github.com/SutuSebastian" + }, + "type": "module", + "exports": { + ".": { + "import": "./dist/index.js", + "require": "./dist/index.cjs" + }, + "./package.json": "./package.json" + }, + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "files": [ + "dist" + ], + "scripts": { + "build": "tsup", + "clean": "rimraf dist node_modules", + "dev": "tsup --watch", + "format": "prettier . --write", + "format:check": "prettier . --check", + "lint": "eslint .", + "lint:fix": "eslint . --fix", + "prepack": "clean-package", + "prepublishOnly": "bun run build", + "release": "changeset publish", + "test": "vitest run", + "typecheck": "tsc", + "version": "changeset version" + }, + "devDependencies": { + "@changesets/changelog-github": "0.5.0", + "@changesets/cli": "2.27.10", + "@ianvs/prettier-plugin-sort-imports": "4.4.0", + "@testing-library/react": "16.0.1", + "@types/bun": "1.1.13", + "@types/react": "18.3.12", + "@typescript-eslint/eslint-plugin": "8.15.0", + "@typescript-eslint/parser": "8.15.0", + "clean-package": "2.2.0", + "eslint": "8.57.0", + "eslint-config-prettier": "9.1.0", + "eslint-plugin-react": "7.37.2", + "eslint-plugin-react-hooks": "5.0.0", + "jsdom": "25.0.1", + "prettier": "3.3.3", + "prettier-plugin-packagejson": "2.5.5", + "rimraf": "6.0.1", + "tsup": "8.3.5", + "typescript": "5.6.3", + "vitest": "2.1.5" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + }, + "clean-package": { + "remove": [ + "devDependencies", + "clean-package" + ], + "replace": { + "scripts": { + "postpublish": "clean-package restore" + } + } + } +} diff --git a/prettier.config.cjs b/prettier.config.cjs new file mode 100644 index 0000000..06093b5 --- /dev/null +++ b/prettier.config.cjs @@ -0,0 +1,14 @@ +/** @type {import('prettier').Config} */ +module.exports = { + plugins: [ + "prettier-plugin-packagejson", + "@ianvs/prettier-plugin-sort-imports", + ], + // sort-imports + importOrder: [ + "", + "", + "^~/(.*)$", + "^[.]", + ], +}; diff --git a/src/index.test.tsx b/src/index.test.tsx new file mode 100644 index 0000000..8689dee --- /dev/null +++ b/src/index.test.tsx @@ -0,0 +1,104 @@ +import { render } from "@testing-library/react"; +import React from "react"; +import { describe, expect, it, vi } from "vitest"; +import { withMemo, withMemoOnce } from "./index"; + +// A simple functional component for testing +const TestComponent: React.FC<{ value: number; onClick: () => void }> = ({ + value, + onClick, +}) => ; + +describe("withMemo", () => { + it("should re-render when a dependency prop changes", () => { + const MemoizedComponent = withMemo(TestComponent, ["value"]); + + const onClickMock = vi.fn(); + const { rerender, container } = render( + , + ); + + // Initial render + expect(container.textContent).toBe("1"); + + // Rerender with different "value" prop + rerender(); + expect(container.textContent).toBe("2"); + }); + + it("should not re-render when a non-dependency prop changes", () => { + const MemoizedComponent = withMemo(TestComponent, ["value"]); + + const onClickMock1 = vi.fn(); + const onClickMock2 = vi.fn(); + const { rerender, container } = render( + , + ); + + // Initial render + expect(container.textContent).toBe("1"); + + // Rerender with the same "value" but different "onClick" + rerender(); + expect(container.textContent).toBe("1"); + }); + + it("should re-render when no dependency props are provided and any prop changes", () => { + const MemoizedComponent = withMemo(TestComponent); + + const onClickMock1 = vi.fn(); + const onClickMock2 = vi.fn(); + const { rerender, container } = render( + , + ); + + // Initial render + expect(container.textContent).toBe("1"); + + // Rerender with a different "value" + rerender(); + expect(container.textContent).toBe("2"); + + // Rerender with a different "onClick" + rerender(); + expect(container.textContent).toBe("2"); + }); + + it("should not throw an error but will compare undefined values", () => { + // @ts-expect-error intended + const MemoizedComponent = withMemo(TestComponent, ["invalidProp"]); + + const { rerender, container } = render( + {}} />, + ); + + expect(container.textContent).toBe("1"); + + // Rerendering will not change the displayed value because "invalidProp" doesn't affect rendering + rerender( {}} />); + expect(container.textContent).toBe("1"); // No re-render triggered. + }); +}); + +describe("withMemoOnce", () => { + it("should not re-render regardless of prop changes", () => { + const MemoizedOnceComponent = withMemoOnce(TestComponent); + + const onClickMock1 = vi.fn(); + const onClickMock2 = vi.fn(); + const { rerender, container } = render( + , + ); + + // Initial render + expect(container.textContent).toBe("1"); + + // Attempt to rerender with different "value" + rerender(); + expect(container.textContent).toBe("1"); + + // Attempt to rerender with different "onClick" + rerender(); + expect(container.textContent).toBe("1"); + }); +}); diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..11b41ab --- /dev/null +++ b/src/index.ts @@ -0,0 +1,67 @@ +import { memo } from "react"; + +/** + * A higher-order function that enhances a React functional component with `React.memo` for memoization. + * + * - If `dependencyProps` is provided, the component will only re-render if any of the specified props in the + * `dependencyProps` array have changed. + * - If `dependencyProps` is not provided, all props will be tested for equality to determine if the component + * should re-render. + * + * Equality checks are performed using `JSON.stringify` for deep comparison, and direct reference comparison for functions. + * + * @template T - The props type of the wrapped component. + * @param {React.FC} Component - The React functional component to be memoized. + * @param {(keyof T)[]} [dependencyProps] - An optional array of prop names to check for equality. If not provided, all props are compared. + * @returns {React.MemoExoticComponent>} A memoized version of the provided component. + */ +export function withMemo( + Component: React.FC, + dependencyProps?: (keyof T)[], +): React.MemoExoticComponent> { + return memo(Component, (prevProps, nextProps) => { + let isEqual = true; + + const allProps = Object.keys(prevProps) as (keyof T)[]; + const propsLength = dependencyProps + ? dependencyProps.length + : allProps.length; + + for (let i = 0; i < propsLength; i++) { + const propName = dependencyProps ? dependencyProps[i] : allProps[i]; + const prevProp = prevProps[propName]; + const nextProp = nextProps[propName]; + + // compare equality between functions + if (typeof prevProp === "function") { + if (prevProp !== nextProp) { + isEqual = false; + break; + } + } + + // compare equality between primitives, arrays, objects... + if (JSON.stringify(prevProp) !== JSON.stringify(nextProp)) { + isEqual = false; + break; + } + } + + return isEqual; + }); +} + +/** + * A higher-order function that enhances a React functional component with `React.memo` for one-time memoization. + * + * The wrapped component will never re-render, regardless of prop changes. + * + * @template T - The props type of the wrapped component. + * @param {React.FC} Component - The React functional component to be memoized. + * @returns {React.MemoExoticComponent>} A memoized version of the provided component that will not re-render. + */ +export function withMemoOnce( + Component: React.FC, +): React.MemoExoticComponent> { + return memo(Component, () => true); +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..5209c86 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "strict": true, + "declaration": false, + "noEmitOnError": true, + "noEmit": true, + "noFallthroughCasesInSwitch": true, + "moduleResolution": "node", + "module": "ESNext", + "target": "ESNext", + "esModuleInterop": true, + "isolatedModules": true, + "lib": ["esnext", "dom"], + "skipLibCheck": true, + "resolveJsonModule": true, + "allowSyntheticDefaultImports": true, + "jsx": "react-jsx" + } +} diff --git a/tsup.config.ts b/tsup.config.ts new file mode 100644 index 0000000..db09caf --- /dev/null +++ b/tsup.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from "tsup"; + +export default defineConfig({ + entryPoints: ["src/index.ts"], + format: ["cjs", "esm"], + dts: true, + outDir: "dist", + clean: true, +}); diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..9f6250a --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + environment: "jsdom", + }, +});