diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index b8f856c7..3c8181f0 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -56,4 +56,4 @@ jobs: - name: build the web App working-directory: apps/readest-app run: | - pnpm build-web + pnpm build-check diff --git a/apps/readest-app/next.config.mjs b/apps/readest-app/next.config.mjs index aac1783c..642af8d4 100644 --- a/apps/readest-app/next.config.mjs +++ b/apps/readest-app/next.config.mjs @@ -1,15 +1,22 @@ import withPWAInit from '@ducanh2912/next-pwa'; -import { initOpenNextCloudflareForDev } from '@opennextjs/cloudflare'; +import withBundleAnalyzer from '@next/bundle-analyzer'; const isDev = process.env['NODE_ENV'] === 'development'; const appPlatform = process.env['NEXT_PUBLIC_APP_PLATFORM']; +/* uncomment this to enable OpenNext Cloudflare for development if (isDev) { - initOpenNextCloudflareForDev(); + const { initOpenNextCloudflareForDev } = await import('@opennextjs/cloudflare'); + initOpenNextCloudflareForDev(); } +*/ const exportOutput = appPlatform !== 'web' && !isDev; +const bundleAnalyzer = withBundleAnalyzer({ + enabled: process.env.ANALYZE === 'true', +}); + /** @type {import('next').NextConfig} */ const nextConfig = { // Ensure Next.js uses SSG instead of SSR @@ -25,6 +32,16 @@ const nextConfig = { // Configure assetPrefix or else the server won't properly resolve your assets. assetPrefix: '', reactStrictMode: true, + transpilePackages: [ + 'i18next-browser-languagedetector', + 'react-i18next', + 'i18next', + '@ducanh2912/next-pwa', + '@tauri-apps', + 'highlight.js', + 'foliate-js', + 'marked', + ], async headers() { return [ { @@ -55,4 +72,4 @@ const withPWA = withPWAInit({ }, }); -export default withPWA(nextConfig); +export default withPWA(bundleAnalyzer(nextConfig)); diff --git a/apps/readest-app/package.json b/apps/readest-app/package.json index dd2e5620..c3eff432 100644 --- a/apps/readest-app/package.json +++ b/apps/readest-app/package.json @@ -35,7 +35,10 @@ "preview": "NEXT_PUBLIC_APP_PLATFORM=web opennextjs-cloudflare build && opennextjs-cloudflare preview", "deploy": "NEXT_PUBLIC_APP_PLATFORM=web opennextjs-cloudflare build && opennextjs-cloudflare deploy", "upload": "NEXT_PUBLIC_APP_PLATFORM=web opennextjs-cloudflare build && opennextjs-cloudflare upload", - "cf-typegen": "wrangler types --env-interface CloudflareEnv cloudflare-env.d.ts" + "cf-typegen": "wrangler types --env-interface CloudflareEnv cloudflare-env.d.ts", + "check:optional-chaining": "count=$(grep -rno '\\?\\.[a-zA-Z_$]' .next/static/chunks/* out/_next/static/chunks/* | wc -l); if [ \"$count\" -gt 0 ]; then echo '❌ Optional chaining found in output!'; exit 1; else echo '✅ No optional chaining found.'; fi", + "check:all": "npm run check:optional-chaining", + "build-check": "pnpm build && pnpm build-web && pnpm check:all" }, "dependencies": { "@aws-sdk/client-s3": "^3.735.0", @@ -93,6 +96,7 @@ "zustand": "5.0.1" }, "devDependencies": { + "@next/bundle-analyzer": "^15.4.2", "@tailwindcss/typography": "^0.5.16", "@tauri-apps/cli": "2.6.0", "@types/cors": "^2.8.17", diff --git a/apps/readest-app/src/utils/book.ts b/apps/readest-app/src/utils/book.ts index a684127b..c4bbb7d9 100644 --- a/apps/readest-app/src/utils/book.ts +++ b/apps/readest-app/src/utils/book.ts @@ -1,7 +1,7 @@ import { EXTS } from '@/libs/document'; import { Book, BookConfig, BookProgress, WritingMode } from '@/types/book'; import { getUserLang, isContentURI, isFileURI, isValidURL, makeSafeFilename } from './misc'; -import { getStorageType } from './object'; +import { getStorageType } from './storage'; import { getDirFromLanguage } from './rtl'; import { SUPPORTED_LANGS } from '@/services/constants'; diff --git a/apps/readest-app/src/utils/error.ts b/apps/readest-app/src/utils/error.ts index 858c493b..6039bb97 100644 --- a/apps/readest-app/src/utils/error.ts +++ b/apps/readest-app/src/utils/error.ts @@ -1,7 +1,7 @@ export const handleGlobalError = (e: Error) => { const isChunkError = e?.message?.includes('Loading chunk'); - if (isChunkError) { + if (!isChunkError) { const now = Date.now(); const lastReload = Number(sessionStorage.getItem('lastErrorReload') || '0'); if (now - lastReload > 60_000) { diff --git a/apps/readest-app/src/utils/object.ts b/apps/readest-app/src/utils/object.ts index 87f95aeb..a94ff9a5 100644 --- a/apps/readest-app/src/utils/object.ts +++ b/apps/readest-app/src/utils/object.ts @@ -1,21 +1,8 @@ import { s3Storage } from './s3'; import { r2Storage } from './r2'; +import { getStorageType } from './storage'; -type ObjectStorageType = 'r2' | 's3'; - -export const getStorageType = (): ObjectStorageType => { - // TODO: do not expose storage type to client - if (process.env['NEXT_PUBLIC_OBJECT_STORAGE_TYPE']) { - return process.env['NEXT_PUBLIC_OBJECT_STORAGE_TYPE'] as ObjectStorageType; - } else { - return 'r2'; - } -}; - -export const getDownloadSignedUrl = async ( - fileKey: string, - expiresIn: number, -) => { +export const getDownloadSignedUrl = async (fileKey: string, expiresIn: number) => { const storageType = getStorageType(); if (storageType === 'r2') { const bucketName = process.env['R2_BUCKET_NAME'] || ''; diff --git a/apps/readest-app/src/utils/storage.ts b/apps/readest-app/src/utils/storage.ts new file mode 100644 index 00000000..937f7e78 --- /dev/null +++ b/apps/readest-app/src/utils/storage.ts @@ -0,0 +1,10 @@ +type ObjectStorageType = 'r2' | 's3'; + +export const getStorageType = (): ObjectStorageType => { + // TODO: do not expose storage type to client + if (process.env['NEXT_PUBLIC_OBJECT_STORAGE_TYPE']) { + return process.env['NEXT_PUBLIC_OBJECT_STORAGE_TYPE'] as ObjectStorageType; + } else { + return 'r2'; + } +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9aa889f1..dc4203c1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -198,6 +198,9 @@ importers: specifier: 5.0.1 version: 5.0.1(@types/react@18.3.12)(react@19.0.0) devDependencies: + '@next/bundle-analyzer': + specifier: ^15.4.2 + version: 15.4.2 '@tailwindcss/typography': specifier: ^0.5.16 version: 0.5.16(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@22.15.31)(typescript@5.7.2))) @@ -1278,6 +1281,10 @@ packages: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} + '@discoveryjs/json-ext@0.5.7': + resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==} + engines: {node: '>=10.0.0'} + '@dotenvx/dotenvx@1.31.0': resolution: {integrity: sha512-GeDxvtjiRuoyWVU9nQneId879zIyNdL05bS7RKiqMkfBSKpHMWHLoRyRqjYWLaXmX/llKO1hTlqHDmatkQAjPA==} hasBin: true @@ -1868,6 +1875,9 @@ packages: resolution: {integrity: sha512-nD6NGa4JbNYSZYsTnLGrqe9Kn/lCkA4ybXt8sx5ojDqZjr2i0TWAHxx/vhgfjX+i3hCdKWufxYwi7CfXqtITSA==} engines: {node: '>= 10'} + '@next/bundle-analyzer@15.4.2': + resolution: {integrity: sha512-kgjecwKDgJ50DZXjVllawD6MuTyxaREB5EUBKbG9NLBHjYu10O/OPL5JqKrBrYdQ9BNVxjGEQoDGcHB76ZjViQ==} + '@next/env@15.3.3': resolution: {integrity: sha512-OdiMrzCl2Xi0VTjiQQUK0Xh7bJHnOuET2s+3V+Y40WJBAXrJeGA3f+I8MZJ/YQ3mVGi5XGR1L66oFlgqXhQ4Vw==} @@ -1980,6 +1990,9 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@polka/url@1.0.0-next.29': + resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + '@rollup/plugin-babel@5.3.1': resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==} engines: {node: '>= 10.0.0'} @@ -3317,6 +3330,10 @@ packages: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} + commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + commander@9.5.0: resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} engines: {node: ^12.20.0 || >=14} @@ -3433,6 +3450,9 @@ packages: dayjs@1.11.13: resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} + debounce@1.2.1: + resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==} + debounce@2.2.0: resolution: {integrity: sha512-Xks6RUDLZFdz8LIdR6q0MTH44k7FikOmnh5xkSjMig6ch45afc8sjTjRQf3P6ax8dMgcQrYO/AR2RGWURrruqw==} engines: {node: '>=18'} @@ -4110,6 +4130,9 @@ packages: resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==} engines: {node: '>=12.0.0'} + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + html-parse-stringify@3.0.1: resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==} @@ -4285,6 +4308,10 @@ packages: resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} engines: {node: '>=0.10.0'} + is-plain-object@5.0.0: + resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} + engines: {node: '>=0.10.0'} + is-promise@4.0.0: resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} @@ -4654,6 +4681,10 @@ packages: mnemonist@0.38.3: resolution: {integrity: sha512-2K9QYubXx/NAjv4VLq1d1Ly8pWNC5L3BrixtdkyTegXWJIqY+zLNDhhX/A+ZwWt70tB1S8H4BE8FLYEFyNoOBw==} + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} @@ -4795,6 +4826,10 @@ packages: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} + opener@1.5.2: + resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==} + hasBin: true + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -5424,6 +5459,10 @@ packages: simple-swizzle@0.2.2: resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + sirv@2.0.4: + resolution: {integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==} + engines: {node: '>= 10'} + slash@5.1.0: resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} engines: {node: '>=14.16'} @@ -5676,6 +5715,10 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -5869,6 +5912,11 @@ packages: webidl-conversions@4.0.2: resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} + webpack-bundle-analyzer@4.10.1: + resolution: {integrity: sha512-s3P7pgexgT/HTUSYgxJyn28A+99mmLq4HsJepMPzu0R8ImJc52QNqaFYW1Z2z2uIb1/J3eYgaAWVpaC+v/1aAQ==} + engines: {node: '>= 10.13.0'} + hasBin: true + webpack-sources@1.4.3: resolution: {integrity: sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==} @@ -6013,6 +6061,18 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + ws@7.5.10: + resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==} + engines: {node: '>=8.3.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + ws@8.18.0: resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} engines: {node: '>=10.0.0'} @@ -8044,6 +8104,8 @@ snapshots: dependencies: '@jridgewell/trace-mapping': 0.3.9 + '@discoveryjs/json-ext@0.5.7': {} + '@dotenvx/dotenvx@1.31.0': dependencies: commander: 11.1.0 @@ -8465,6 +8527,13 @@ snapshots: '@napi-rs/canvas-win32-x64-msvc': 0.1.70 optional: true + '@next/bundle-analyzer@15.4.2': + dependencies: + webpack-bundle-analyzer: 4.10.1 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + '@next/env@15.3.3': {} '@next/eslint-plugin-next@15.0.3': @@ -8570,6 +8639,8 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@polka/url@1.0.0-next.29': {} + '@rollup/plugin-babel@5.3.1(@babel/core@7.26.7)(rollup@2.79.2)': dependencies: '@babel/core': 7.26.7 @@ -10261,6 +10332,8 @@ snapshots: commander@4.1.1: {} + commander@7.2.0: {} + commander@9.5.0: {} common-tags@1.8.2: {} @@ -10379,6 +10452,8 @@ snapshots: dayjs@1.11.13: {} + debounce@1.2.1: {} + debounce@2.2.0: {} debug@3.2.7: @@ -11226,6 +11301,8 @@ snapshots: highlight.js@11.11.1: {} + html-escaper@2.0.2: {} + html-parse-stringify@3.0.1: dependencies: void-elements: 3.1.0 @@ -11400,6 +11477,8 @@ snapshots: dependencies: isobject: 3.0.1 + is-plain-object@5.0.0: {} + is-promise@4.0.0: {} is-regex@1.2.0: @@ -11711,6 +11790,8 @@ snapshots: dependencies: obliterator: 1.6.1 + mrmime@2.0.1: {} + ms@2.1.2: {} ms@2.1.3: {} @@ -11850,6 +11931,8 @@ snapshots: dependencies: mimic-fn: 2.1.0 + opener@1.5.2: {} + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -12501,6 +12584,12 @@ snapshots: dependencies: is-arrayish: 0.3.2 + sirv@2.0.4: + dependencies: + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + slash@5.1.0: {} smob@1.5.0: {} @@ -12782,6 +12871,8 @@ snapshots: toidentifier@1.0.1: {} + totalist@3.0.1: {} + tr46@0.0.3: {} tr46@1.0.1: @@ -13002,6 +13093,25 @@ snapshots: webidl-conversions@4.0.2: {} + webpack-bundle-analyzer@4.10.1: + dependencies: + '@discoveryjs/json-ext': 0.5.7 + acorn: 8.15.0 + acorn-walk: 8.3.4 + commander: 7.2.0 + debounce: 1.2.1 + escape-string-regexp: 4.0.0 + gzip-size: 6.0.0 + html-escaper: 2.0.2 + is-plain-object: 5.0.0 + opener: 1.5.2 + picocolors: 1.1.1 + sirv: 2.0.4 + ws: 7.5.10 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + webpack-sources@1.4.3: dependencies: source-list-map: 2.0.1 @@ -13313,6 +13423,8 @@ snapshots: wrappy@1.0.2: {} + ws@7.5.10: {} + ws@8.18.0: {} ws@8.18.2: {}