diff --git a/package-lock.json b/package-lock.json index 2b466965..8aa6a34c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,7 +34,6 @@ "browser-encrypt-attachment": "0.3.0", "chroma-js": "3.1.2", "classnames": "2.3.2", - "dateformat": "5.0.3", "dayjs": "1.11.10", "domhandler": "5.0.3", "emojibase": "15.3.1", @@ -116,7 +115,7 @@ "wait-on": "9.0.10" }, "engines": { - "node": ">=22.0.0" + "node": ">=22.12.0" } }, "node_modules/@ampproject/remapping": { @@ -8266,14 +8265,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/dateformat": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-5.0.3.tgz", - "integrity": "sha512-Kvr6HmPXUMerlLcLF+Pwq3K7apHpYmGDVqrxcDasBg86UcKeTSNWbEzU8bwdXnxnR44FtMhJAxI4Bov6Y/KUfA==", - "engines": { - "node": ">=12.20" - } - }, "node_modules/dayjs": { "version": "1.11.10", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", diff --git a/package.json b/package.json index a73f08a9..7d7d3552 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,6 @@ "browser-encrypt-attachment": "0.3.0", "chroma-js": "3.1.2", "classnames": "2.3.2", - "dateformat": "5.0.3", "dayjs": "1.11.10", "domhandler": "5.0.3", "emojibase": "15.3.1", diff --git a/src/app/components/editor/autocomplete/EmoticonAutocomplete.tsx b/src/app/components/editor/autocomplete/EmoticonAutocomplete.tsx index d358ff7d..a2387eb8 100644 --- a/src/app/components/editor/autocomplete/EmoticonAutocomplete.tsx +++ b/src/app/components/editor/autocomplete/EmoticonAutocomplete.tsx @@ -11,7 +11,8 @@ import { onTabPress } from '../../../utils/keyboard'; import { createEmoticonElement, moveCursor, replaceWithElement } from '../utils'; import { useRecentEmoji } from '../../../hooks/useRecentEmoji'; import { useRelevantImagePacks } from '../../../hooks/useImagePacks'; -import { IEmoji, emojis } from '../../../plugins/emoji'; +import { IEmoji } from '../../../plugins/emoji'; +import { emojis } from '../../../plugins/emoji-data'; import { useKeyDown } from '../../../hooks/useKeyDown'; import { mxcUrlToHttp } from '../../../utils/matrix'; import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication'; diff --git a/src/app/components/emoji-board/EmojiBoard.tsx b/src/app/components/emoji-board/EmojiBoard.tsx index d5a76c71..be10a212 100644 --- a/src/app/components/emoji-board/EmojiBoard.tsx +++ b/src/app/components/emoji-board/EmojiBoard.tsx @@ -15,7 +15,8 @@ import { isKeyHotkey } from 'is-hotkey'; import { Room } from 'matrix-js-sdk'; import { atom, PrimitiveAtom, useAtom, useSetAtom } from 'jotai'; import { useVirtualizer } from '@tanstack/react-virtual'; -import { IEmoji, emojiGroups, emojis } from '../../plugins/emoji'; +import { IEmoji } from '../../plugins/emoji'; +import { emojiGroups, emojis } from '../../plugins/emoji-data'; import { useEmojiGroupLabels } from './useEmojiGroupLabels'; import { useEmojiGroupIcons } from './useEmojiGroupIcons'; import { preventScrollWithArrowKey, stopPropagation } from '../../utils/keyboard'; diff --git a/src/app/components/mobile-tabs-pager/MobileTabsLayout.tsx b/src/app/components/mobile-tabs-pager/MobileTabsLayout.tsx index 5f1cfcb5..d7ed9ccc 100644 --- a/src/app/components/mobile-tabs-pager/MobileTabsLayout.tsx +++ b/src/app/components/mobile-tabs-pager/MobileTabsLayout.tsx @@ -1,9 +1,17 @@ -import React from 'react'; +import React, { Suspense } from 'react'; import { Outlet, useMatch } from 'react-router-dom'; import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize'; import { isNativePlatform } from '../../utils/capacitor'; import { BOTS_PATH, CHANNELS_PATH, CHANNELS_SPACE_PATH, DIRECT_PATH } from '../../pages/paths'; -import { MobileTabsPager } from './MobileTabsPager'; + +// MobileTabsPager is the ONLY static importer of the Channels and Bots feature +// modules. It renders only on `mobile && native` (below), so lazy-loading it +// here removes the static edge that otherwise pinned Channels + Bots into the +// boot bundle for every target. On native the chunk streams from the local APK +// filesystem (no network), so a null Suspense fallback is imperceptible. +const MobileTabsPager = React.lazy(() => + import('./MobileTabsPager').then((m) => ({ default: m.MobileTabsPager })) +); // Router-level wrapper around the three listing tabs (/direct/, // /channels/, /bots/). When all of (mobile breakpoint, Capacitor @@ -44,5 +52,9 @@ export function MobileTabsLayout() { if (!(mobile && native) || !onListingRoot) { return ; } - return ; + return ( + + + + ); } diff --git a/src/app/components/uia-stages/ReCaptchaStage.tsx b/src/app/components/uia-stages/ReCaptchaStage.tsx index 68b3fcf4..78dcc98f 100644 --- a/src/app/components/uia-stages/ReCaptchaStage.tsx +++ b/src/app/components/uia-stages/ReCaptchaStage.tsx @@ -1,9 +1,13 @@ -import React from 'react'; +import React, { Suspense } from 'react'; import { Dialog, Text, Box, Button, config } from 'folds'; import { AuthType } from 'matrix-js-sdk'; -import ReCAPTCHA from 'react-google-recaptcha'; import { StageComponentProps } from './types'; +// react-google-recaptcha (+ its grecaptcha loader) is only ever rendered in +// the registration UIA captcha stage — a cold, rare path. Lazy-loading it +// keeps it out of the boot bundle. +const ReCAPTCHA = React.lazy(() => import('react-google-recaptcha')); + function ReCaptchaErrorDialog({ title, message, @@ -57,7 +61,9 @@ export function ReCaptchaStageDialog({ stageData, submitAuthDict, onCancel }: St Please check the box below to proceed. - + Loading…}> + + ); diff --git a/src/app/features/common-settings/permissions/PowersEditor.tsx b/src/app/features/common-settings/permissions/PowersEditor.tsx index c94a0f7b..c3fc3c7b 100644 --- a/src/app/features/common-settings/permissions/PowersEditor.tsx +++ b/src/app/features/common-settings/permissions/PowersEditor.tsx @@ -1,4 +1,11 @@ -import React, { FormEventHandler, MouseEventHandler, useCallback, useMemo, useState } from 'react'; +import React, { + FormEventHandler, + MouseEventHandler, + Suspense, + useCallback, + useMemo, + useState, +} from 'react'; import { Box, Text, @@ -37,7 +44,6 @@ import { useRoom } from '../../../hooks/useRoom'; import { HexColorPickerPopOut } from '../../../components/HexColorPickerPopOut'; import { PowerColorBadge, PowerIcon } from '../../../components/power'; import { UseStateProvider } from '../../../components/UseStateProvider'; -import { EmojiBoard } from '../../../components/emoji-board'; import { useImagePackRooms } from '../../../hooks/useImagePackRooms'; import { roomToParentsAtom } from '../../../state/room/roomToParents'; import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication'; @@ -52,6 +58,17 @@ import { BetaNoticeBadge } from '../../../components/BetaNoticeBadge'; import { getPowerTagIconSrc } from '../../../hooks/useMemberPowerTag'; import { creatorsSupported } from '../../../utils/matrix'; +// Lazy-loaded: EmojiBoard pulls the heavy emoji picker dataset (`emoji-data`, +// ~506 KB compact.json). PowersEditor is reachable from the boot graph +// (RoomSettingsRenderer / SpaceSettingsRenderer are mounted at the app root), +// so a static import here would drag `emoji-data` into the initial bundle. The +// board only mounts inside the PopOut below when the user opens the icon +// picker, so deferring it keeps `emoji-data` off boot (the chat composer's own +// static EmojiBoard import keeps the chunk in the lazy Room bundle). +const EmojiBoard = React.lazy(() => + import('../../../components/emoji-board').then((m) => ({ default: m.EmojiBoard })) +); + type EditPowerProps = { maxPower: number; power?: number; @@ -208,23 +225,25 @@ function EditPower({ maxPower, power, tag, onSave, onClose }: EditPowerProps) { position="Bottom" anchor={cords} content={ - { - setTagIcon({ key }); - setCords(undefined); - }} - onCustomEmojiSelect={(mxc) => { - setTagIcon({ key: mxc }); - setCords(undefined); - }} - requestClose={() => { - setCords(undefined); - }} - /> + + { + setTagIcon({ key }); + setCords(undefined); + }} + onCustomEmojiSelect={(mxc) => { + setTagIcon({ key: mxc }); + setCords(undefined); + }} + requestClose={() => { + setCords(undefined); + }} + /> + } >