From 657c6f49349f202d6a243ec2550f9e2f7bdb4d8a Mon Sep 17 00:00:00 2001 From: Amir Meshkin Date: Mon, 24 Nov 2025 08:44:23 -0500 Subject: [PATCH] sadf --- README.md | 4 +- package-lock.json | 22 ++++++---- package.json | 4 +- src/Breadcrumbs.tsx | 2 +- src/hooks/useReactRouterBreadcrumbs.ts | 57 +++++++++++++++++++++++++- 5 files changed, 75 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 85ac8d2..7db3e88 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,11 @@ > MUI 7 Breadcrumbs with **Next.js Link** support, optional **URL-based generation**, and **built-in SEO microdata**. Router-agnostic with zero runtime deps. | Item | Value | Badge | -| --------------- | ---------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| --------------- | ---------------------------------------------------------------------------- |----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **GitHub repo** | [github.com/ameshkin/nextcrumbs](https://github.com/ameshkin/nextcrumbs) | — | | **CI** | `.github/workflows/ci.yml` | [![CI — main](https://img.shields.io/github/actions/workflow/status/ameshkin/nextcrumbs/ci.yml?branch=main\&label=ci%20\(main\))](https://github.com/ameshkin/nextcrumbs/actions/workflows/ci.yml) [![CI — dev](https://img.shields.io/github/actions/workflow/status/ameshkin/nextcrumbs/ci.yml?branch=dev\&label=ci%20\(dev\))](https://github.com/ameshkin/nextcrumbs/actions/workflows/ci.yml) | | **npm package** | [@ameshkin/nextcrumbs](https://www.npmjs.com/package/@ameshkin/nextcrumbs) | [![npm](https://img.shields.io/npm/v/@ameshkin/nextcrumbs.svg)](https://www.npmjs.com/package/@ameshkin/nextcrumbs) | -| **Install** | `npm i @ameshkin/nextcrumbs` | [![install test](https://img.shields.io/github/actions/workflow/status/ameshkin/nextcrumbs/install.yml?branch=main\&label=install%20test)](https://github.com/ameshkin/nextcrumbs/actions/workflows/install.yml) | +| **Install** | `npm i @ameshkin/nextcrumbs` | [![install test](https://img.shields.io/github/actions/workflow/status/ameshkin/nextcrumbs/ci-main.yml?branch=main\&label=install%20test)](https://github.com/ameshkin/nextcrumbs/actions/workflows/ci-main.yml) | | **Peer deps** | `@mui/material@^7`, `@mui/icons-material@^7`, `react@>=18`, `react-dom@>=18` | ![peer deps](https://img.shields.io/badge/peer_deps-MUI%207%20%7C%20Icons%207%20%7C%20React%2018-blue) | | **Types** | TypeScript | ![types](https://img.shields.io/badge/types-TypeScript-blue.svg) | | **License** | MIT | [![license](https://img.shields.io/badge/license-MIT-black.svg)](LICENSE) | diff --git a/package-lock.json b/package-lock.json index 405c178..687e2a9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,25 +1,23 @@ { "name": "@ameshkin/nextcrumbs", - "version": "0.0.5", + "version": "0.1.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@ameshkin/nextcrumbs", - "version": "0.0.5", + "version": "0.1.2", "license": "MIT", - "dependencies": { - "react-router-dom": "^7.9.5" - }, "devDependencies": { "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.0", "jsdom": "^22.1.0", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-router-dom": "^7.9.5", "tsup": "^8.5.0", "typescript": "^5.9.3", - "vitest": "^4.0.5" + "vitest": "^4.0.6" }, "engines": { "node": ">=20.19 <21 || >=22.12" @@ -34,7 +32,8 @@ "@mui/icons-material": ">=7", "@mui/material": ">=7", "react": ">=18", - "react-dom": ">=18" + "react-dom": ">=18", + "react-router-dom": ">=6 || >=7" }, "peerDependenciesMeta": { "@emotion/react": { @@ -42,6 +41,9 @@ }, "@emotion/styled": { "optional": false + }, + "react-router-dom": { + "optional": true } } }, @@ -1857,6 +1859,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "dev": true, "engines": { "node": ">=18" } @@ -3056,6 +3059,7 @@ "version": "7.9.5", "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.9.5.tgz", "integrity": "sha512-JmxqrnBZ6E9hWmf02jzNn9Jm3UqyeimyiwzD69NjxGySG6lIz/1LVPsoTCwN7NBX2XjCEa1LIX5EMz1j2b6u6A==", + "dev": true, "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" @@ -3077,6 +3081,7 @@ "version": "7.9.5", "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.9.5.tgz", "integrity": "sha512-mkEmq/K8tKN63Ae2M7Xgz3c9l9YNbY+NHH6NNeUmLA3kDkhKXRsNb/ZpxaEunvGo2/3YXdk5EJU3Hxp3ocaBPw==", + "dev": true, "dependencies": { "react-router": "7.9.5" }, @@ -3241,7 +3246,8 @@ "node_modules/set-cookie-parser": { "version": "2.7.2", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", - "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==" + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "dev": true }, "node_modules/shebang-command": { "version": "2.0.0", diff --git a/package.json b/package.json index 158a04c..5a61805 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ameshkin/nextcrumbs", - "version": "0.1.0", + "version": "0.1.2", "description": "MUI 7 breadcrumbs with Next.js Link support, optional URL auto-generation, and SEO microdata.", "license": "MIT", "type": "module", @@ -60,6 +60,6 @@ "funding": { "type": "github", "url": "https://github.com/sponsors/ameshkin" }, "engines": { "node": ">=20.19 <21 || >=22.12" }, "packageManager": "npm@10.5.0", - "keywords": ["mui", "breadcrumbs", "nextjs", "react", "material-ui", "seo", "schema.org", "app router"], + "keywords": ["mui", "breadcrumbs", "nextjs", "react", "material-ui", "seo", "schema.org", "app router", "react router"], "publishConfig": { "access": "public" } } diff --git a/src/Breadcrumbs.tsx b/src/Breadcrumbs.tsx index e177bc7..14bd898 100644 --- a/src/Breadcrumbs.tsx +++ b/src/Breadcrumbs.tsx @@ -97,7 +97,7 @@ export default function Breadcrumbs({ }) => ( {icon && {icon}} - {label} + {label} ); diff --git a/src/hooks/useReactRouterBreadcrumbs.ts b/src/hooks/useReactRouterBreadcrumbs.ts index 9d2bebf..4a2e2f6 100644 --- a/src/hooks/useReactRouterBreadcrumbs.ts +++ b/src/hooks/useReactRouterBreadcrumbs.ts @@ -11,7 +11,61 @@ */ import * as React from "react" import type { ReactNode } from "react" -import { useLocation } from "react-router-dom" + +// Lazy import pattern to avoid requiring react-router-dom when not used +// We use a function that accesses the module at runtime to avoid +// webpack trying to resolve it at build time when it's not installed +let useLocationHook: (() => { pathname: string }) | null = null + +function getUseLocation(): () => { pathname: string } { + if (useLocationHook) return useLocationHook + + // Access react-router-dom dynamically using a pattern that's harder + // for webpack to statically analyze. We construct the module name + // in a way that makes it less obvious to static analysis. + try { + // Split the module name to make it harder for webpack to detect + const parts = ["react", "router", "dom"] + const mod = parts.join("-") + + // Try to access the module - this will work if it's available + // @ts-ignore - accessing optional dependency dynamically + let routerModule: any = null + + // Try CommonJS require (for Node.js/CommonJS environments) + if (typeof require !== 'undefined') { + try { + routerModule = require(mod) + } catch { + // require failed, module not available + } + } + + // For ESM environments, the module should be available through + // normal module resolution if installed. Since we can't use + // dynamic import() synchronously, we rely on the consuming + // project to have it available or configure webpack appropriately. + if (!routerModule) { + throw new Error("react-router-dom not available") + } + + if (!routerModule.useLocation || typeof routerModule.useLocation !== 'function') { + throw new Error("useLocation not found in react-router-dom") + } + + const hook = routerModule.useLocation + useLocationHook = hook + return hook + } catch (err: any) { + // If react-router-dom is not available, throw a helpful error + throw new Error( + "useReactRouterBreadcrumbs requires 'react-router-dom' to be installed. " + + "Please install it: npm install react-router-dom. " + + `If you're using Next.js and don't need react-router-dom, you can configure ` + + `webpack to ignore it or install it as a dev dependency.` + ) + } +} export type BreadcrumbItem = { label: string @@ -30,6 +84,7 @@ export type ReactRouterBreadcrumbsOptions = { } export function useReactRouterBreadcrumbs(options?: ReactRouterBreadcrumbsOptions): BreadcrumbItem[] { + const useLocation = getUseLocation() const { pathname } = useLocation() const { rootLabel, basePath, decode = true, exclude = [], mapSegmentLabel } = options || {}