diff --git a/.gitignore b/.gitignore index 3063f07d..a16f4e18 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ lib node_modules +.idea/ diff --git a/README.md b/README.md index cac92ba9..f2964898 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ This package is a fork of [React Helmet](https://github.com/nfl/react-helmet). `react-helmet` relies on `react-side-effect`, which is not thread-safe. If you are doing anything asynchronous on the server, you need Helmet to encapsulate data on a per-request basis, this package does just that. +Supports React 16.6+, including React 17, 18, and 19. + ## Usage **New is 1.0.0:** No more default export! `import { Helmet } from 'react-helmet-async'` diff --git a/__tests__/utils.tsx b/__tests__/utils.tsx index 82a97546..07cec08a 100644 --- a/__tests__/utils.tsx +++ b/__tests__/utils.tsx @@ -1,8 +1,7 @@ import type { ReactNode } from 'react'; -import React, { StrictMode } from 'react'; +import React, { StrictMode, act } from 'react'; import { createRoot } from 'react-dom/client'; import type { Root } from 'react-dom/client'; -import { act } from 'react-dom/test-utils'; import Provider from '../src/Provider'; diff --git a/build.ts b/build.ts index 0a3bba5f..a8596a36 100644 --- a/build.ts +++ b/build.ts @@ -6,6 +6,7 @@ const shared = { entryPoints: ['src/index.tsx'], bundle: true, external: Object.keys(dependencies).concat(Object.keys(peerDependencies)), + jsx: 'automatic' as const, }; build({ diff --git a/package.json b/package.json index abdeb947..623b2c8a 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "@types/eslint": "8.44.8", "@types/invariant": "2.2.37", "@types/jsdom": "21.1.6", - "@types/react": "18.2.39", + "@types/react": "^19.0.1", "@types/shallowequal": "1.1.5", "@vitejs/plugin-react": "4.2.0", "esbuild": "0.19.8", @@ -37,8 +37,8 @@ "jsdom": "22.1.0", "prettier": "3.1.0", "raf": "3.4.1", - "react": "18.2.0", - "react-dom": "18.2.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", "rimraf": "5.0.5", "tsx": "4.6.1", "typescript": "5.2.2", @@ -46,7 +46,7 @@ "vitest": "0.34.6" }, "peerDependencies": { - "react": "^16.6.0 || ^17.0.0 || ^18.0.0" + "react": "^16.6.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "scripts": { "clean": "rimraf lib", @@ -57,6 +57,7 @@ "test-update": "yarn test -u", "compile": "yarn run clean && NODE_ENV=production tsx build.ts && yarn types", "prepare": "yarn compile && husky install", - "types": "tsc src/index.tsx --jsx react --declaration --esModuleInterop --allowJs --emitDeclarationOnly --outDir lib" - } + "types": "tsc src/index.tsx --jsx react-jsx --declaration --esModuleInterop --allowJs --emitDeclarationOnly --outDir lib" + }, + "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" } diff --git a/src/Provider.tsx b/src/Provider.tsx index 432a83f7..d33b4857 100644 --- a/src/Provider.tsx +++ b/src/Provider.tsx @@ -1,12 +1,12 @@ import type { PropsWithChildren } from 'react'; -import React, { Component } from 'react'; +import { Component, createContext } from 'react'; import HelmetData, { isDocument } from './HelmetData'; import type { HelmetServerState } from './types'; const defaultValue = {}; -export const Context = React.createContext(defaultValue); +export const Context = createContext(defaultValue); interface ProviderProps { context?: { diff --git a/src/index.tsx b/src/index.tsx index df2a1893..e5b5f32f 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,5 +1,5 @@ import type { PropsWithChildren, ReactElement, ReactNode } from 'react'; -import React, { Component } from 'react'; +import { Component, Children } from 'react'; import fastCompare from 'react-fast-compare'; import invariant from 'invariant'; @@ -143,12 +143,14 @@ export class Helmet extends Component> { mapChildrenToProps(children: ReactNode, newProps: Props) { let arrayTypeChildren = {}; - React.Children.forEach(children as JSX.Element, (child: ReactElement) => { + Children.forEach(children as JSX.Element, (child: ReactElement) => { if (!child || !child.props) { return; } - const { children: nestedChildren, ...childProps } = child.props; + // React 19 changed how props are accessed - need to handle both old and new behavior + const props = child.props as any; + const { children: nestedChildren, ...childProps } = props; // convert React props to HTML attributes const newChildProps = Object.keys(childProps).reduce((obj: Props, key) => { obj[HTML_TAG_MAP[key] || key] = childProps[key]; diff --git a/src/server.ts b/src/server.ts index 52106e88..31c5c777 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,4 +1,4 @@ -import React from 'react'; +import { createElement } from 'react'; import { HELMET_ATTRIBUTE, @@ -93,7 +93,7 @@ const generateTitleAsReactComponent = (_type: string, title: string, attributes: }; const props = convertElementAttributesToReactProps(attributes, initProps); - return [React.createElement(TAG_NAMES.TITLE, props, title)]; + return [createElement(TAG_NAMES.TITLE, props, title)]; }; const generateTagsAsReactComponent = (type: string, tags: any[]) => @@ -118,7 +118,7 @@ const generateTagsAsReactComponent = (type: string, tags: any[]) => } }); - return React.createElement(type, mappedTag); + return createElement(type, mappedTag); }); const getMethodsForTag = (type: string, tags: any, encode = true) => { diff --git a/tsconfig.json b/tsconfig.json index 885ab3fb..c8590cc9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,7 +6,7 @@ "types": ["vitest/globals"], "isolatedModules": true, "esModuleInterop": true, - "jsx": "react", + "jsx": "react-jsx", "module": "CommonJS", "moduleResolution": "node", "resolveJsonModule": true, diff --git a/vitest.config.ts b/vitest.config.ts index 6adbb0d0..6e308b4f 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -10,7 +10,9 @@ interface VitestConfigExport extends UserConfig { } export default defineConfig({ - plugins: [react()], + plugins: [react({ + jsxRuntime: 'automatic', + })], test: { globals: true, environment: 'jsdom', diff --git a/yarn.lock b/yarn.lock index 0784da3e..cf9f539b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1090,7 +1090,7 @@ dependencies: "@types/react" "*" -"@types/react@*", "@types/react@18.2.39": +"@types/react@*": version "18.2.39" resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.39.tgz#744bee99e053ad61fe74eb8b897f3ab5b19a7e25" integrity sha512-Oiw+ppED6IremMInLV4HXGbfbG6GyziY3kqAwJYOR0PNbkYDmLWQA3a95EhdSmamsvbkJN96ZNN+YD+fGjzSBA== @@ -1099,6 +1099,13 @@ "@types/scheduler" "*" csstype "^3.0.2" +"@types/react@^19.0.1": + version "19.2.7" + resolved "https://registry.yarnpkg.com/@types/react/-/react-19.2.7.tgz#84e62c0f23e8e4e5ac2cadcea1ffeacccae7f62f" + integrity sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg== + dependencies: + csstype "^3.2.2" + "@types/scheduler@*": version "0.16.8" resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.8.tgz#ce5ace04cfeabe7ef87c0091e50752e36707deff" @@ -1762,6 +1769,11 @@ csstype@^3.0.2: resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b" integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== +csstype@^3.2.2: + version "3.2.3" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.2.3.tgz#ec48c0f3e993e50648c86da559e2610995cf989a" + integrity sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ== + damerau-levenshtein@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7" @@ -3432,7 +3444,7 @@ lodash@^4.17.15: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: +loose-envify@^1.0.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -4008,13 +4020,12 @@ raf@3.4.1: dependencies: performance-now "^2.1.0" -react-dom@18.2.0: - version "18.2.0" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" - integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== +react-dom@^19.0.0: + version "19.2.3" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.2.3.tgz#f0b61d7e5c4a86773889fcc1853af3ed5f215b17" + integrity sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg== dependencies: - loose-envify "^1.1.0" - scheduler "^0.23.0" + scheduler "^0.27.0" react-fast-compare@^3.2.2: version "3.2.2" @@ -4041,12 +4052,10 @@ react-refresh@^0.14.0: resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e" integrity sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ== -react@18.2.0: - version "18.2.0" - resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" - integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== - dependencies: - loose-envify "^1.1.0" +react@^19.0.0: + version "19.2.3" + resolved "https://registry.yarnpkg.com/react/-/react-19.2.3.tgz#d83e5e8e7a258cf6b4fe28640515f99b87cd19b8" + integrity sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA== read-pkg-up@^7.0.1: version "7.0.1" @@ -4275,12 +4284,10 @@ saxes@^6.0.0: dependencies: xmlchars "^2.2.0" -scheduler@^0.23.0: - version "0.23.0" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe" - integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw== - dependencies: - loose-envify "^1.1.0" +scheduler@^0.27.0: + version "0.27.0" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.27.0.tgz#0c4ef82d67d1e5c1e359e8fc76d3a87f045fe5bd" + integrity sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q== "semver@2 || 3 || 4 || 5": version "5.7.2" @@ -4424,7 +4431,16 @@ stop-iteration-iterator@^1.0.0: dependencies: internal-slot "^1.0.4" -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -4491,7 +4507,14 @@ string_decoder@^1.1.1: dependencies: safe-buffer "~5.2.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -4997,7 +5020,16 @@ why-is-node-running@^2.2.2: siginfo "^2.0.0" stackback "0.0.2" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==