diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index bf1be02f0..4c09eddbb 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,4 +1,4 @@ - # 📝 Why & how @@ -14,4 +14,4 @@ Please follow the template so that the reviewers can easily understand what the - [ ] Documented in this PR how to use the feature or replicate the bug. -- [ ] Documented in this PR how you fixed or created the feature. +- [ ] Documented in this PR how you fixed an issue or created the feature. diff --git a/bun.lock b/bun.lock index ea7e3521e..0d28b37b5 100644 --- a/bun.lock +++ b/bun.lock @@ -10,10 +10,9 @@ "@react-native-async-storage/async-storage": "^2.2.0", "@react-native-picker/picker": "^2.11.2", "@sentry/react": "^10.12.0", - "@vercel/blob": "^0.27.3", + "es-toolkit": "^1.39.10", "expo": "54.0.9", "expo-font": "^14.0.8", - "lodash": "^4.17.21", "next": "^15.5.3", "node-emoji": "^2.2.0", "react": "19.1.1", @@ -30,8 +29,8 @@ "@expo/next-adapter": "^6.0.0", "@next/bundle-analyzer": "^15.5.3", "@types/bun": "^1.2.22", - "@types/lodash": "^4.17.20", "@types/react": "^19.1.13", + "@vercel/blob": "^0.27.3", "ajv-cli": "^5.0.0", "browserslist": "^4.26.2", "cheerio": "^1.1.2", @@ -587,8 +586,6 @@ "@types/linkifyjs": ["@types/linkifyjs@2.1.7", "", { "dependencies": { "@types/react": "*" } }, "sha512-+SIYXs1lajyD7t/2+V9GLfdFlc/6Nr2tr65kjA2F5oOzBlPH+NiPqySJDHzREoGcL91Au9Qef8M5JdZiRXsaJw=="], - "@types/lodash": ["@types/lodash@4.17.20", "", {}, "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA=="], - "@types/node": ["@types/node@24.3.0", "", { "dependencies": { "undici-types": "~7.10.0" } }, "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow=="], "@types/react": ["@types/react@19.1.13", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ=="], @@ -1019,6 +1016,8 @@ "es-to-primitive": ["es-to-primitive@1.3.0", "", { "dependencies": { "is-callable": "^1.2.7", "is-date-object": "^1.0.5", "is-symbol": "^1.0.4" } }, "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g=="], + "es-toolkit": ["es-toolkit@1.39.10", "", {}, "sha512-E0iGnTtbDhkeczB0T+mxmoVlT4YNweEKBLq7oaU4p11mecdsZpNWOglI4895Vh4usbQ+LsJiuLuI2L0Vdmfm2w=="], + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="], @@ -1445,8 +1444,6 @@ "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], - "lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="], - "lodash.debounce": ["lodash.debounce@4.0.8", "", {}, "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="], "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], @@ -1963,7 +1960,7 @@ "unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="], - "undici": ["undici@7.15.0", "", {}, "sha512-7oZJCPvvMvTd0OlqWsIxTuItTpJBpU1tcbVl24FMn3xt3+VSunwUasmfPJRE57oNO1KsZ4PgA1xTdAX4hq8NyQ=="], + "undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="], "undici-types": ["undici-types@7.10.0", "", {}, "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag=="], @@ -2167,14 +2164,14 @@ "@typescript-eslint/utils/@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.7.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw=="], - "@vercel/blob/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="], - "babel-plugin-polyfill-corejs2/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], "better-opn/open": ["open@8.4.2", "", { "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", "is-wsl": "^2.2.0" } }, "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ=="], "caller-callsite/callsites": ["callsites@2.0.0", "", {}, "sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ=="], + "cheerio/undici": ["undici@7.15.0", "", {}, "sha512-7oZJCPvvMvTd0OlqWsIxTuItTpJBpU1tcbVl24FMn3xt3+VSunwUasmfPJRE57oNO1KsZ4PgA1xTdAX4hq8NyQ=="], + "chromium-edge-launcher/mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="], "cli-truncate/string-width": ["string-width@8.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.0", "strip-ansi": "^7.1.0" } }, "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg=="], diff --git a/components/Library/MetaData.tsx b/components/Library/MetaData.tsx index 814f2d5b6..be36e321e 100644 --- a/components/Library/MetaData.tsx +++ b/components/Library/MetaData.tsx @@ -211,18 +211,20 @@ export function MetaData({ library, secondary }: Props) { const data = generateSecondaryData(library, isDark).filter(Boolean); return ( <> - {data.map(({ id, icon, content }, i) => ( - - {icon} - {content} - - ))} + {data + .filter(entry => !!entry) + .map(({ id, icon, content }, i) => ( + + {icon} + {content} + + ))} ); } else { diff --git a/next.config.js b/next.config.ts similarity index 63% rename from next.config.js rename to next.config.ts index 47643159b..1e8772081 100644 --- a/next.config.js +++ b/next.config.ts @@ -1,9 +1,21 @@ import { withExpo } from '@expo/next-adapter'; import BundleAnalyzer from '@next/bundle-analyzer'; +import type { NextConfig } from 'next'; import withPlugins from 'next-compose-plugins'; import withFonts from 'next-fonts'; import withImages from 'next-images'; +const PACKAGES_TO_OPTIMIZE = [ + '@expo/html-elements', + '@react-native-picker/picker', + '@sentry/*', + 'node-emoji', + 'react-native', + 'react-native-safe-area-context', + 'react-native-svg', + 'react-native-web', +]; + const withBundleAnalyzer = BundleAnalyzer({ enabled: process.env.ANALYZE === 'true', }); @@ -12,40 +24,21 @@ export default withPlugins([withExpo, withImages, withFonts, withBundleAnalyzer] productionBrowserSourceMaps: true, reactStrictMode: true, poweredByHeader: false, - devIndicators: { - enabled: false, - }, + devIndicators: false, eslint: { ignoreDuringBuilds: true, }, images: { disableStaticImages: true, }, - transpilePackages: [ - '@expo/html-elements', - '@react-native-picker/picker', - '@sentry/react', - 'react-native-safe-area-context', - 'react-native-svg', - 'react-native-web', - 'react-native-web-hooks', - 'react-native', - ], + transpilePackages: PACKAGES_TO_OPTIMIZE, experimental: { forceSwcTransforms: true, webpackBuildWorker: true, browserDebugInfoInTerminal: true, clientSegmentCache: true, - optimizePackageImports: [ - '@expo/html-elements', - '@react-native-picker/picker', - '@sentry/react', - 'react-native-safe-area-context', - 'react-native-svg', - 'react-native-web', - 'react-native-web-hooks', - 'react-native', - ], + useLightningcss: true, + optimizePackageImports: PACKAGES_TO_OPTIMIZE, }, async headers() { return [ @@ -58,4 +51,4 @@ export default withPlugins([withExpo, withImages, withFonts, withBundleAnalyzer] }, ]; }, -}); +} satisfies NextConfig); diff --git a/package.json b/package.json index 422955e66..b75ad8222 100644 --- a/package.json +++ b/package.json @@ -24,10 +24,9 @@ "@react-native-async-storage/async-storage": "^2.2.0", "@react-native-picker/picker": "^2.11.2", "@sentry/react": "^10.12.0", - "@vercel/blob": "^0.27.3", "expo": "54.0.9", "expo-font": "^14.0.8", - "lodash": "^4.17.21", + "es-toolkit": "^1.39.10", "next": "^15.5.3", "node-emoji": "^2.2.0", "react": "19.1.1", @@ -44,8 +43,8 @@ "@expo/next-adapter": "^6.0.0", "@next/bundle-analyzer": "^15.5.3", "@types/bun": "^1.2.22", - "@types/lodash": "^4.17.20", "@types/react": "^19.1.13", + "@vercel/blob": "^0.27.3", "ajv-cli": "^5.0.0", "browserslist": "^4.26.2", "cheerio": "^1.1.2", diff --git a/pages/api/libraries/check.ts b/pages/api/libraries/check.ts index 74884147b..855ec1973 100644 --- a/pages/api/libraries/check.ts +++ b/pages/api/libraries/check.ts @@ -2,10 +2,19 @@ import { type NextApiRequest, type NextApiResponse } from 'next'; import data from '~/assets/data.json'; import { type DataAssetType } from '~/types'; -import { getNewArchSupportStatus } from '~/util/newArchStatus'; +import { getNewArchSupportStatus, NewArchSupportStatus } from '~/util/newArchStatus'; + +type CheckResultsType = Record< + string, + { + unmaintained?: boolean; + newArchitecture: NewArchSupportStatus; + } +>; // Copy data into an object that is keyed by npm package name for faster lookup -const dataByNpmPackage = {}; +const dataByNpmPackage: CheckResultsType = {}; + (data as DataAssetType).libraries.forEach(library => { dataByNpmPackage[library.npmPkg] = { unmaintained: library.unmaintained, @@ -30,7 +39,7 @@ export default function handler(req: NextApiRequest, res: NextApiResponse) { } res.statusCode = 200; - const result = {}; + const result: CheckResultsType = {}; packages.forEach(pkgName => { result[pkgName] = dataByNpmPackage[pkgName]; }); diff --git a/pages/api/libraries/index.ts b/pages/api/libraries/index.ts index 255c8889e..f983311f2 100644 --- a/pages/api/libraries/index.ts +++ b/pages/api/libraries/index.ts @@ -1,15 +1,14 @@ -import drop from 'lodash/drop'; -import take from 'lodash/take'; +import { drop, take } from 'es-toolkit'; import { NextApiRequest, NextApiResponse } from 'next'; import data from '~/assets/data.json'; -import { type LibraryType, QueryOrder } from '~/types'; +import { type DataAssetType, QueryOrder } from '~/types'; import { NUM_PER_PAGE } from '~/util/Constants'; import { parseQueryParams } from '~/util/parseQueryParams'; import { handleFilterLibraries } from '~/util/search'; import * as Sorting from '~/util/sorting'; -const originalData = [...data.libraries] as LibraryType[]; +const originalData = [...(data as DataAssetType).libraries]; const getData = () => ({ updated: Sorting.updated([...originalData]), added: [...originalData.reverse()], diff --git a/scripts/build-and-score-data.ts b/scripts/build-and-score-data.ts index 36436d3d6..28beb667e 100644 --- a/scripts/build-and-score-data.ts +++ b/scripts/build-and-score-data.ts @@ -1,6 +1,6 @@ import { BlobAccessError, list, put } from '@vercel/blob'; import { fetch } from 'bun'; -import chunk from 'lodash/chunk'; +import { chunk } from 'es-toolkit'; import fs from 'node:fs'; import path from 'node:path'; diff --git a/scripts/calculate-score.ts b/scripts/calculate-score.ts index 17417f922..052c3fe53 100644 --- a/scripts/calculate-score.ts +++ b/scripts/calculate-score.ts @@ -95,10 +95,9 @@ export function calculateDirectoryScore(data: LibraryType) { }; } -function getCombinedPopularity(data: LibraryType) { - const { subscribers, forks, stars } = data.github.stats; - const { downloads } = data.npm; - return subscribers * 50 + forks * 25 + stars * 10 + downloads / 100; +function getCombinedPopularity({ github, npm }: LibraryType) { + const { subscribers, forks, stars } = github.stats; + return subscribers * 50 + forks * 25 + stars * 10 + (npm?.downloads ?? 0) / 100; } function getUpdatedDaysAgo(data: LibraryType) { diff --git a/scripts/cleanup-libraries-json.ts b/scripts/cleanup-libraries-json.ts index 509678f11..73426d56c 100644 --- a/scripts/cleanup-libraries-json.ts +++ b/scripts/cleanup-libraries-json.ts @@ -1,6 +1,4 @@ -import identity from 'lodash/identity'; -import omit from 'lodash/omit'; -import pickBy from 'lodash/pickBy'; +import { identity, omit, pickBy } from 'es-toolkit'; import fs from 'node:fs'; import path from 'node:path'; @@ -11,15 +9,15 @@ const LIBRARIES_JSON_PATH = path.join('react-native-libraries.json'); const emptyPropertiesToKeep = ['newArchitecture']; -function removeEmptyArray(lib: LibraryDataEntryType, key: string) { - return lib[key] && !lib[key].length ? (omit(lib, key) as LibraryDataEntryType) : lib; +function removeEmptyArray(lib: LibraryDataEntryType, key: 'examples' | 'images') { + return lib[key] && !lib[key].length ? omit(lib, [key]) : lib; } -const processedLibraries = (libraries as LibraryDataEntryType[]) +const processedLibraries = libraries // Remove redundant `npmPkg` for libraries with the correct GitHub repository name - .map(lib => + .map((lib: LibraryDataEntryType) => lib.npmPkg && !lib.npmPkg.includes('/') && lib.githubUrl.endsWith(`/${lib.npmPkg}`) - ? omit(lib, 'npmPkg') + ? omit(lib, ['npmPkg']) : lib ) // Remove empty arrays @@ -31,7 +29,7 @@ const processedLibraries = (libraries as LibraryDataEntryType[]) if (emptyPropertiesToKeep.includes(key)) { return true; } else { - return identity(value); + return !!identity(value); } }) ); diff --git a/scripts/helpers.ts b/scripts/helpers.ts index 2a6645a3b..e215c13e2 100644 --- a/scripts/helpers.ts +++ b/scripts/helpers.ts @@ -58,7 +58,7 @@ export function processTopics(topics?: string[]) { } function splitAndGetLastChunk(value: string, delimiter = '/') { - return value.split(delimiter).at(-1).toLowerCase(); + return value.split(delimiter).at(-1)?.toLowerCase(); } export function hasMismatchedPackageData(library: LibraryType) { @@ -66,7 +66,7 @@ export function hasMismatchedPackageData(library: LibraryType) { if (library.github.registry && library.github.registry !== 'https://registry.npmjs.org/') { const registryScope = splitAndGetLastChunk(library.github.registry); - if (registryScope.startsWith('@')) { + if (registryScope?.startsWith('@')) { return library.github?.name.replace(`${registryScope}/`, '') !== desiredName; } } diff --git a/scripts/validate-new-entries.ts b/scripts/validate-new-entries.ts index 64e73b51b..cfbd30e0c 100644 --- a/scripts/validate-new-entries.ts +++ b/scripts/validate-new-entries.ts @@ -1,6 +1,5 @@ import { fetch } from 'bun'; -import differenceWith from 'lodash/differenceWith'; -import isEqual from 'lodash/isEqual'; +import { differenceWith, isEqual } from 'es-toolkit'; import { fetchGithubData } from './fetch-github-data'; import { fetchNpmDownloadData } from './fetch-npm-download-data'; diff --git a/types/app.d.ts b/types/app.d.ts new file mode 100644 index 000000000..352d73fda --- /dev/null +++ b/types/app.d.ts @@ -0,0 +1,3 @@ +declare module 'next-compose-plugins'; +declare module 'next-fonts'; +declare module 'next-images'; diff --git a/types/index.ts b/types/index.ts index 1a87454f3..ed77171e1 100644 --- a/types/index.ts +++ b/types/index.ts @@ -123,7 +123,7 @@ export type LibraryDataEntryType = { fireos?: boolean; tvos?: boolean; visionos?: boolean; - unmaintained?: boolean | string; + unmaintained?: boolean; dev?: boolean; template?: boolean; newArchitecture?: boolean | 'new-arch-only';