perf(bundle): code-split heavy routes and the emoji picker and add cacheable vendor chunks to shrink first-load
This commit is contained in:
parent
297b55f693
commit
067417050c
13 changed files with 305 additions and 158 deletions
11
package-lock.json
generated
11
package-lock.json
generated
|
|
@ -34,7 +34,6 @@
|
||||||
"browser-encrypt-attachment": "0.3.0",
|
"browser-encrypt-attachment": "0.3.0",
|
||||||
"chroma-js": "3.1.2",
|
"chroma-js": "3.1.2",
|
||||||
"classnames": "2.3.2",
|
"classnames": "2.3.2",
|
||||||
"dateformat": "5.0.3",
|
|
||||||
"dayjs": "1.11.10",
|
"dayjs": "1.11.10",
|
||||||
"domhandler": "5.0.3",
|
"domhandler": "5.0.3",
|
||||||
"emojibase": "15.3.1",
|
"emojibase": "15.3.1",
|
||||||
|
|
@ -116,7 +115,7 @@
|
||||||
"wait-on": "9.0.10"
|
"wait-on": "9.0.10"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=22.0.0"
|
"node": ">=22.12.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@ampproject/remapping": {
|
"node_modules/@ampproject/remapping": {
|
||||||
|
|
@ -8266,14 +8265,6 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"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": {
|
"node_modules/dayjs": {
|
||||||
"version": "1.11.10",
|
"version": "1.11.10",
|
||||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz",
|
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz",
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,6 @@
|
||||||
"browser-encrypt-attachment": "0.3.0",
|
"browser-encrypt-attachment": "0.3.0",
|
||||||
"chroma-js": "3.1.2",
|
"chroma-js": "3.1.2",
|
||||||
"classnames": "2.3.2",
|
"classnames": "2.3.2",
|
||||||
"dateformat": "5.0.3",
|
|
||||||
"dayjs": "1.11.10",
|
"dayjs": "1.11.10",
|
||||||
"domhandler": "5.0.3",
|
"domhandler": "5.0.3",
|
||||||
"emojibase": "15.3.1",
|
"emojibase": "15.3.1",
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,8 @@ import { onTabPress } from '../../../utils/keyboard';
|
||||||
import { createEmoticonElement, moveCursor, replaceWithElement } from '../utils';
|
import { createEmoticonElement, moveCursor, replaceWithElement } from '../utils';
|
||||||
import { useRecentEmoji } from '../../../hooks/useRecentEmoji';
|
import { useRecentEmoji } from '../../../hooks/useRecentEmoji';
|
||||||
import { useRelevantImagePacks } from '../../../hooks/useImagePacks';
|
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 { useKeyDown } from '../../../hooks/useKeyDown';
|
||||||
import { mxcUrlToHttp } from '../../../utils/matrix';
|
import { mxcUrlToHttp } from '../../../utils/matrix';
|
||||||
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
|
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,8 @@ import { isKeyHotkey } from 'is-hotkey';
|
||||||
import { Room } from 'matrix-js-sdk';
|
import { Room } from 'matrix-js-sdk';
|
||||||
import { atom, PrimitiveAtom, useAtom, useSetAtom } from 'jotai';
|
import { atom, PrimitiveAtom, useAtom, useSetAtom } from 'jotai';
|
||||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
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 { useEmojiGroupLabels } from './useEmojiGroupLabels';
|
||||||
import { useEmojiGroupIcons } from './useEmojiGroupIcons';
|
import { useEmojiGroupIcons } from './useEmojiGroupIcons';
|
||||||
import { preventScrollWithArrowKey, stopPropagation } from '../../utils/keyboard';
|
import { preventScrollWithArrowKey, stopPropagation } from '../../utils/keyboard';
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,17 @@
|
||||||
import React from 'react';
|
import React, { Suspense } from 'react';
|
||||||
import { Outlet, useMatch } from 'react-router-dom';
|
import { Outlet, useMatch } from 'react-router-dom';
|
||||||
import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
|
import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
|
||||||
import { isNativePlatform } from '../../utils/capacitor';
|
import { isNativePlatform } from '../../utils/capacitor';
|
||||||
import { BOTS_PATH, CHANNELS_PATH, CHANNELS_SPACE_PATH, DIRECT_PATH } from '../../pages/paths';
|
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/,
|
// Router-level wrapper around the three listing tabs (/direct/,
|
||||||
// /channels/, /bots/). When all of (mobile breakpoint, Capacitor
|
// /channels/, /bots/). When all of (mobile breakpoint, Capacitor
|
||||||
|
|
@ -44,5 +52,9 @@ export function MobileTabsLayout() {
|
||||||
if (!(mobile && native) || !onListingRoot) {
|
if (!(mobile && native) || !onListingRoot) {
|
||||||
return <Outlet />;
|
return <Outlet />;
|
||||||
}
|
}
|
||||||
return <MobileTabsPager />;
|
return (
|
||||||
|
<Suspense fallback={null}>
|
||||||
|
<MobileTabsPager />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
import React from 'react';
|
import React, { Suspense } from 'react';
|
||||||
import { Dialog, Text, Box, Button, config } from 'folds';
|
import { Dialog, Text, Box, Button, config } from 'folds';
|
||||||
import { AuthType } from 'matrix-js-sdk';
|
import { AuthType } from 'matrix-js-sdk';
|
||||||
import ReCAPTCHA from 'react-google-recaptcha';
|
|
||||||
import { StageComponentProps } from './types';
|
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({
|
function ReCaptchaErrorDialog({
|
||||||
title,
|
title,
|
||||||
message,
|
message,
|
||||||
|
|
@ -57,7 +61,9 @@ export function ReCaptchaStageDialog({ stageData, submitAuthDict, onCancel }: St
|
||||||
<Dialog>
|
<Dialog>
|
||||||
<Box style={{ padding: config.space.S400 }} direction="Column" gap="400">
|
<Box style={{ padding: config.space.S400 }} direction="Column" gap="400">
|
||||||
<Text>Please check the box below to proceed.</Text>
|
<Text>Please check the box below to proceed.</Text>
|
||||||
<ReCAPTCHA sitekey={publicKey} onChange={handleChange} />
|
<Suspense fallback={<Text size="T200">Loading…</Text>}>
|
||||||
|
<ReCAPTCHA sitekey={publicKey} onChange={handleChange} />
|
||||||
|
</Suspense>
|
||||||
</Box>
|
</Box>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,11 @@
|
||||||
import React, { FormEventHandler, MouseEventHandler, useCallback, useMemo, useState } from 'react';
|
import React, {
|
||||||
|
FormEventHandler,
|
||||||
|
MouseEventHandler,
|
||||||
|
Suspense,
|
||||||
|
useCallback,
|
||||||
|
useMemo,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Text,
|
Text,
|
||||||
|
|
@ -37,7 +44,6 @@ import { useRoom } from '../../../hooks/useRoom';
|
||||||
import { HexColorPickerPopOut } from '../../../components/HexColorPickerPopOut';
|
import { HexColorPickerPopOut } from '../../../components/HexColorPickerPopOut';
|
||||||
import { PowerColorBadge, PowerIcon } from '../../../components/power';
|
import { PowerColorBadge, PowerIcon } from '../../../components/power';
|
||||||
import { UseStateProvider } from '../../../components/UseStateProvider';
|
import { UseStateProvider } from '../../../components/UseStateProvider';
|
||||||
import { EmojiBoard } from '../../../components/emoji-board';
|
|
||||||
import { useImagePackRooms } from '../../../hooks/useImagePackRooms';
|
import { useImagePackRooms } from '../../../hooks/useImagePackRooms';
|
||||||
import { roomToParentsAtom } from '../../../state/room/roomToParents';
|
import { roomToParentsAtom } from '../../../state/room/roomToParents';
|
||||||
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
|
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
|
||||||
|
|
@ -52,6 +58,17 @@ import { BetaNoticeBadge } from '../../../components/BetaNoticeBadge';
|
||||||
import { getPowerTagIconSrc } from '../../../hooks/useMemberPowerTag';
|
import { getPowerTagIconSrc } from '../../../hooks/useMemberPowerTag';
|
||||||
import { creatorsSupported } from '../../../utils/matrix';
|
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 = {
|
type EditPowerProps = {
|
||||||
maxPower: number;
|
maxPower: number;
|
||||||
power?: number;
|
power?: number;
|
||||||
|
|
@ -208,23 +225,25 @@ function EditPower({ maxPower, power, tag, onSave, onClose }: EditPowerProps) {
|
||||||
position="Bottom"
|
position="Bottom"
|
||||||
anchor={cords}
|
anchor={cords}
|
||||||
content={
|
content={
|
||||||
<EmojiBoard
|
<Suspense fallback={null}>
|
||||||
imagePackRooms={imagePackRooms}
|
<EmojiBoard
|
||||||
returnFocusOnDeactivate={false}
|
imagePackRooms={imagePackRooms}
|
||||||
allowTextCustomEmoji={false}
|
returnFocusOnDeactivate={false}
|
||||||
addToRecentEmoji={false}
|
allowTextCustomEmoji={false}
|
||||||
onEmojiSelect={(key) => {
|
addToRecentEmoji={false}
|
||||||
setTagIcon({ key });
|
onEmojiSelect={(key) => {
|
||||||
setCords(undefined);
|
setTagIcon({ key });
|
||||||
}}
|
setCords(undefined);
|
||||||
onCustomEmojiSelect={(mxc) => {
|
}}
|
||||||
setTagIcon({ key: mxc });
|
onCustomEmojiSelect={(mxc) => {
|
||||||
setCords(undefined);
|
setTagIcon({ key: mxc });
|
||||||
}}
|
setCords(undefined);
|
||||||
requestClose={() => {
|
}}
|
||||||
setCords(undefined);
|
requestClose={() => {
|
||||||
}}
|
setCords(undefined);
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ import { Provider as JotaiProvider } from 'jotai';
|
||||||
import { OverlayContainerProvider, PopOutContainerProvider, TooltipContainerProvider } from 'folds';
|
import { OverlayContainerProvider, PopOutContainerProvider, TooltipContainerProvider } from 'folds';
|
||||||
import { RouterProvider } from 'react-router-dom';
|
import { RouterProvider } from 'react-router-dom';
|
||||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
|
||||||
|
|
||||||
import { ClientConfigLoader } from '../components/ClientConfigLoader';
|
import { ClientConfigLoader } from '../components/ClientConfigLoader';
|
||||||
import { ClientConfigProvider } from '../hooks/useClientConfig';
|
import { ClientConfigProvider } from '../hooks/useClientConfig';
|
||||||
|
|
@ -16,6 +15,15 @@ import { installPushLanguageBridge } from '../utils/pushLanguageBridge';
|
||||||
|
|
||||||
const queryClient = new QueryClient();
|
const queryClient = new QueryClient();
|
||||||
|
|
||||||
|
// Dev-only: react-query-devtools must never ship to production. The dynamic
|
||||||
|
// import lives in a branch Vite statically eliminates (import.meta.env.DEV →
|
||||||
|
// false in prod), so Rollup drops the chunk entirely from the prod bundle.
|
||||||
|
const ReactQueryDevtools = import.meta.env.DEV
|
||||||
|
? React.lazy(() =>
|
||||||
|
import('@tanstack/react-query-devtools').then((m) => ({ default: m.ReactQueryDevtools }))
|
||||||
|
)
|
||||||
|
: null;
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const screenSize = useScreenSize();
|
const screenSize = useScreenSize();
|
||||||
useCompositionEndTracking();
|
useCompositionEndTracking();
|
||||||
|
|
@ -48,7 +56,11 @@ function App() {
|
||||||
<JotaiProvider>
|
<JotaiProvider>
|
||||||
<RouterProvider router={createRouter(clientConfig, screenSize)} />
|
<RouterProvider router={createRouter(clientConfig, screenSize)} />
|
||||||
</JotaiProvider>
|
</JotaiProvider>
|
||||||
<ReactQueryDevtools initialIsOpen={false} />
|
{ReactQueryDevtools && (
|
||||||
|
<React.Suspense fallback={null}>
|
||||||
|
<ReactQueryDevtools initialIsOpen={false} />
|
||||||
|
</React.Suspense>
|
||||||
|
)}
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
</ClientConfigProvider>
|
</ClientConfigProvider>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import React from 'react';
|
import React, { Suspense } from 'react';
|
||||||
|
import { Box, Spinner } from 'folds';
|
||||||
import {
|
import {
|
||||||
Navigate,
|
Navigate,
|
||||||
Outlet,
|
Outlet,
|
||||||
|
|
@ -53,13 +54,9 @@ import { getMxIdServer, isUserId } from '../utils/matrix';
|
||||||
import { ClientBindAtoms, ClientLayout, ClientRoot } from './client';
|
import { ClientBindAtoms, ClientLayout, ClientRoot } from './client';
|
||||||
import { HomeRouteRoomProvider } from './client/home';
|
import { HomeRouteRoomProvider } from './client/home';
|
||||||
import { Direct, DirectCreate, DirectRouteRoomProvider } from './client/direct';
|
import { Direct, DirectCreate, DirectRouteRoomProvider } from './client/direct';
|
||||||
import { BotExperienceHost, Bots } from './client/bots';
|
import { ChannelPickPlaceholder } from './client/channels/ChannelPickPlaceholder';
|
||||||
import { Channels, ChannelsRootNav, ChannelPickPlaceholder } from './client/channels';
|
|
||||||
import { RouteSpaceProvider, Space, SpaceRouteRoomProvider, SpaceSearch } from './client/space';
|
import { RouteSpaceProvider, Space, SpaceRouteRoomProvider, SpaceSearch } from './client/space';
|
||||||
import { Explore, FeaturedRooms, PublicRooms } from './client/explore';
|
|
||||||
import { setAfterLoginRedirectPath } from './afterLoginRedirectPath';
|
import { setAfterLoginRedirectPath } from './afterLoginRedirectPath';
|
||||||
import { Room } from '../features/room';
|
|
||||||
import { Lobby } from '../features/lobby';
|
|
||||||
import { WelcomePage } from './client/WelcomePage';
|
import { WelcomePage } from './client/WelcomePage';
|
||||||
import { PageRoot } from '../components/page';
|
import { PageRoot } from '../components/page';
|
||||||
import { ScreenSize } from '../hooks/useScreenSize';
|
import { ScreenSize } from '../hooks/useScreenSize';
|
||||||
|
|
@ -73,11 +70,10 @@ import { RoomSettingsRenderer } from '../features/room-settings';
|
||||||
import { ClientRoomsNotificationPreferences } from './client/ClientRoomsNotificationPreferences';
|
import { ClientRoomsNotificationPreferences } from './client/ClientRoomsNotificationPreferences';
|
||||||
import { SpaceSettingsRenderer } from '../features/space-settings';
|
import { SpaceSettingsRenderer } from '../features/space-settings';
|
||||||
import { CreateRoomModalRenderer } from '../features/create-room';
|
import { CreateRoomModalRenderer } from '../features/create-room';
|
||||||
import { Create } from './client/create';
|
|
||||||
import { CreateSpaceModalRenderer } from '../features/create-space';
|
import { CreateSpaceModalRenderer } from '../features/create-space';
|
||||||
import { SearchModalRenderer } from '../features/search';
|
import { SearchModalRenderer } from '../features/search';
|
||||||
import { useShareTargetReceiver } from '../hooks/useShareTargetReceiver';
|
import { useShareTargetReceiver } from '../hooks/useShareTargetReceiver';
|
||||||
import { SettingsScreen } from '../features/settings';
|
import { SettingsScreen } from '../features/settings/SettingsScreen';
|
||||||
import { getFallbackSession } from '../state/sessions';
|
import { getFallbackSession } from '../state/sessions';
|
||||||
import { CallEmbedProvider } from '../components/CallEmbedProvider';
|
import { CallEmbedProvider } from '../components/CallEmbedProvider';
|
||||||
import { useIncomingRtcNotifications } from '../hooks/useIncomingRtcNotifications';
|
import { useIncomingRtcNotifications } from '../hooks/useIncomingRtcNotifications';
|
||||||
|
|
@ -129,6 +125,59 @@ function UserLinkRedirect() {
|
||||||
return <Navigate to={withSearchParam(getDirectCreatePath(), params)} replace />;
|
return <Navigate to={withSearchParam(getDirectCreatePath(), params)} replace />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Route-level code-splitting. Each heavy page is loaded on first navigation
|
||||||
|
// instead of in the boot bundle — this pulls the timeline (Room), lobby,
|
||||||
|
// explore, bots and channels subsystems off first load. Imports point at the
|
||||||
|
// concrete source file (not the barrel) so each chunk stays tight.
|
||||||
|
// `routeSuspense` wraps every usage so only the content area shows the fallback
|
||||||
|
// while the chunk streams in — surrounding nav/chrome stays mounted.
|
||||||
|
//
|
||||||
|
// NOTE: SettingsScreen is intentionally NOT lazy here. The settings UI
|
||||||
|
// (`features/settings/Settings`) is already pulled into the boot graph by
|
||||||
|
// `Direct` → `MobileSettingsHorseshoe` (the mobile settings sheet, mounted in
|
||||||
|
// the boot listing view), so a Router-level lazy split produces a Rollup
|
||||||
|
// "dynamically + statically imported" warning and zero size win. Splitting
|
||||||
|
// settings off boot requires lazy-loading `<Settings>` inside its two hosts
|
||||||
|
// (MobileSettingsHorseshoe + SettingsScreen) — deferred as a separate change.
|
||||||
|
const Room = React.lazy(() => import('../features/room/Room').then((m) => ({ default: m.Room })));
|
||||||
|
const Lobby = React.lazy(() =>
|
||||||
|
import('../features/lobby/Lobby').then((m) => ({ default: m.Lobby }))
|
||||||
|
);
|
||||||
|
const Explore = React.lazy(() =>
|
||||||
|
import('./client/explore/Explore').then((m) => ({ default: m.Explore }))
|
||||||
|
);
|
||||||
|
const FeaturedRooms = React.lazy(() =>
|
||||||
|
import('./client/explore/Featured').then((m) => ({ default: m.FeaturedRooms }))
|
||||||
|
);
|
||||||
|
const PublicRooms = React.lazy(() =>
|
||||||
|
import('./client/explore/Server').then((m) => ({ default: m.PublicRooms }))
|
||||||
|
);
|
||||||
|
const Bots = React.lazy(() => import('./client/bots/Bots').then((m) => ({ default: m.Bots })));
|
||||||
|
const BotExperienceHost = React.lazy(() =>
|
||||||
|
import('./client/bots/BotExperienceHost').then((m) => ({ default: m.BotExperienceHost }))
|
||||||
|
);
|
||||||
|
const Channels = React.lazy(() =>
|
||||||
|
import('./client/channels/Channels').then((m) => ({ default: m.Channels }))
|
||||||
|
);
|
||||||
|
const ChannelsRootNav = React.lazy(() =>
|
||||||
|
import('./client/channels/Channels').then((m) => ({ default: m.ChannelsRootNav }))
|
||||||
|
);
|
||||||
|
const Create = React.lazy(() =>
|
||||||
|
import('./client/create/Create').then((m) => ({ default: m.Create }))
|
||||||
|
);
|
||||||
|
|
||||||
|
function RouteSuspenseFallback() {
|
||||||
|
return (
|
||||||
|
<Box grow="Yes" style={{ height: '100%' }} alignItems="Center" justifyContent="Center">
|
||||||
|
<Spinner variant="Secondary" size="600" />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const routeSuspense = (node: React.ReactNode) => (
|
||||||
|
<Suspense fallback={<RouteSuspenseFallback />}>{node}</Suspense>
|
||||||
|
);
|
||||||
|
|
||||||
export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize) => {
|
export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize) => {
|
||||||
const { hashRouter } = clientConfig;
|
const { hashRouter } = clientConfig;
|
||||||
const mobile = screenSize === ScreenSize.Mobile;
|
const mobile = screenSize === ScreenSize.Mobile;
|
||||||
|
|
@ -225,11 +274,7 @@ export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize)
|
||||||
<Route path={_SEARCH_PATH} element={<Navigate to={DIRECT_PATH} replace />} />
|
<Route path={_SEARCH_PATH} element={<Navigate to={DIRECT_PATH} replace />} />
|
||||||
<Route
|
<Route
|
||||||
path={_ROOM_PATH}
|
path={_ROOM_PATH}
|
||||||
element={
|
element={<HomeRouteRoomProvider>{routeSuspense(<Room />)}</HomeRouteRoomProvider>}
|
||||||
<HomeRouteRoomProvider>
|
|
||||||
<Room />
|
|
||||||
</HomeRouteRoomProvider>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</Route>
|
</Route>
|
||||||
{/* Mobile + Capacitor horizontal swipe pager. The layout-route
|
{/* Mobile + Capacitor horizontal swipe pager. The layout-route
|
||||||
|
|
@ -258,11 +303,7 @@ export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize)
|
||||||
<Route path={_CREATE_PATH} element={<DirectCreate />} />
|
<Route path={_CREATE_PATH} element={<DirectCreate />} />
|
||||||
<Route
|
<Route
|
||||||
path={_ROOM_PATH}
|
path={_ROOM_PATH}
|
||||||
element={
|
element={<DirectRouteRoomProvider>{routeSuspense(<Room />)}</DirectRouteRoomProvider>}
|
||||||
<DirectRouteRoomProvider>
|
|
||||||
<Room />
|
|
||||||
</DirectRouteRoomProvider>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</Route>
|
</Route>
|
||||||
{/* Bots reuses StreamHeader segments. /bots/* is reserved before SPACE_PATH so deep URLs don't fall to /:spaceIdOrAlias/. */}
|
{/* Bots reuses StreamHeader segments. /bots/* is reserved before SPACE_PATH so deep URLs don't fall to /:spaceIdOrAlias/. */}
|
||||||
|
|
@ -272,7 +313,7 @@ export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize)
|
||||||
<PageRoot
|
<PageRoot
|
||||||
nav={
|
nav={
|
||||||
<MobileFriendlyPageNav path={BOTS_PATH}>
|
<MobileFriendlyPageNav path={BOTS_PATH}>
|
||||||
<Bots />
|
{routeSuspense(<Bots />)}
|
||||||
</MobileFriendlyPageNav>
|
</MobileFriendlyPageNav>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
@ -281,7 +322,7 @@ export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{mobile ? null : <Route index element={<WelcomePage />} />}
|
{mobile ? null : <Route index element={<WelcomePage />} />}
|
||||||
<Route path=":botId" element={<BotExperienceHost />} />
|
<Route path=":botId" element={routeSuspense(<BotExperienceHost />)} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/bots/*" element={<Navigate to={BOTS_PATH} replace />} />
|
<Route path="/bots/*" element={<Navigate to={BOTS_PATH} replace />} />
|
||||||
{/* Channels segment. /channels/* is reserved before SPACE_PATH so the
|
{/* Channels segment. /channels/* is reserved before SPACE_PATH so the
|
||||||
|
|
@ -302,7 +343,7 @@ export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize)
|
||||||
<PageRoot
|
<PageRoot
|
||||||
nav={
|
nav={
|
||||||
<MobileFriendlyPageNav path={CHANNELS_PATH}>
|
<MobileFriendlyPageNav path={CHANNELS_PATH}>
|
||||||
<ChannelsRootNav />
|
{routeSuspense(<ChannelsRootNav />)}
|
||||||
</MobileFriendlyPageNav>
|
</MobileFriendlyPageNav>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
@ -317,7 +358,7 @@ export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize)
|
||||||
<PageRoot
|
<PageRoot
|
||||||
nav={
|
nav={
|
||||||
<MobileFriendlyPageNav path={CHANNELS_SPACE_PATH}>
|
<MobileFriendlyPageNav path={CHANNELS_SPACE_PATH}>
|
||||||
<Channels />
|
{routeSuspense(<Channels />)}
|
||||||
</MobileFriendlyPageNav>
|
</MobileFriendlyPageNav>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
@ -329,11 +370,7 @@ export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize)
|
||||||
{mobile ? null : <Route index element={<ChannelPickPlaceholder />} />}
|
{mobile ? null : <Route index element={<ChannelPickPlaceholder />} />}
|
||||||
<Route
|
<Route
|
||||||
path={CHANNELS_ROOM_PATH.slice(CHANNELS_SPACE_PATH.length)}
|
path={CHANNELS_ROOM_PATH.slice(CHANNELS_SPACE_PATH.length)}
|
||||||
element={
|
element={<SpaceRouteRoomProvider>{routeSuspense(<Room />)}</SpaceRouteRoomProvider>}
|
||||||
<SpaceRouteRoomProvider>
|
|
||||||
<Room />
|
|
||||||
</SpaceRouteRoomProvider>
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{/* Thread drawer URL — same Room element renders, drawer
|
{/* Thread drawer URL — same Room element renders, drawer
|
||||||
opens by reading `:rootId` via useParams. The
|
opens by reading `:rootId` via useParams. The
|
||||||
|
|
@ -383,15 +420,11 @@ export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize)
|
||||||
element={<WelcomePage />}
|
element={<WelcomePage />}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Route path={_LOBBY_PATH} element={<Lobby />} />
|
<Route path={_LOBBY_PATH} element={routeSuspense(<Lobby />)} />
|
||||||
<Route path={_SEARCH_PATH} element={<SpaceSearch />} />
|
<Route path={_SEARCH_PATH} element={<SpaceSearch />} />
|
||||||
<Route
|
<Route
|
||||||
path={_ROOM_PATH}
|
path={_ROOM_PATH}
|
||||||
element={
|
element={<SpaceRouteRoomProvider>{routeSuspense(<Room />)}</SpaceRouteRoomProvider>}
|
||||||
<SpaceRouteRoomProvider>
|
|
||||||
<Room />
|
|
||||||
</SpaceRouteRoomProvider>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</Route>
|
</Route>
|
||||||
<Route
|
<Route
|
||||||
|
|
@ -400,7 +433,7 @@ export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize)
|
||||||
<PageRoot
|
<PageRoot
|
||||||
nav={
|
nav={
|
||||||
<MobileFriendlyPageNav path={EXPLORE_PATH}>
|
<MobileFriendlyPageNav path={EXPLORE_PATH}>
|
||||||
<Explore />
|
{routeSuspense(<Explore />)}
|
||||||
</MobileFriendlyPageNav>
|
</MobileFriendlyPageNav>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
@ -415,10 +448,10 @@ export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize)
|
||||||
element={<WelcomePage />}
|
element={<WelcomePage />}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Route path={_FEATURED_PATH} element={<FeaturedRooms />} />
|
<Route path={_FEATURED_PATH} element={routeSuspense(<FeaturedRooms />)} />
|
||||||
<Route path={_SERVER_PATH} element={<PublicRooms />} />
|
<Route path={_SERVER_PATH} element={routeSuspense(<PublicRooms />)} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path={CREATE_PATH} element={<Create />} />
|
<Route path={CREATE_PATH} element={routeSuspense(<Create />)} />
|
||||||
{/* /settings shares the DIRECT_PATH shell — left page-nav stays
|
{/* /settings shares the DIRECT_PATH shell — left page-nav stays
|
||||||
the DM list, the right pane swaps the chat outlet for the
|
the DM list, the right pane swaps the chat outlet for the
|
||||||
Settings UI. The horseshoe rounded TL/BL on the right pane
|
Settings UI. The horseshoe rounded TL/BL on the right pane
|
||||||
|
|
|
||||||
86
src/app/plugins/emoji-data.ts
Normal file
86
src/app/plugins/emoji-data.ts
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
import emojisData from 'emojibase-data/en/compact.json';
|
||||||
|
import { EmojiGroupId, IEmoji, IEmojiGroup, getShortcodesFor } from './emoji';
|
||||||
|
|
||||||
|
// Heavy emoji dataset (~506 KB raw `compact.json`). Split out of `./emoji` so
|
||||||
|
// it is bundled only into the lazy chunks that render the emoji picker /
|
||||||
|
// autocomplete (EmojiBoard, EmoticonAutocomplete) and never into the initial
|
||||||
|
// app bundle. The module-level processing below runs once on first import of
|
||||||
|
// this file.
|
||||||
|
export const emojiGroups: IEmojiGroup[] = [
|
||||||
|
{
|
||||||
|
id: EmojiGroupId.People,
|
||||||
|
order: 0,
|
||||||
|
emojis: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: EmojiGroupId.Nature,
|
||||||
|
order: 1,
|
||||||
|
emojis: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: EmojiGroupId.Food,
|
||||||
|
order: 2,
|
||||||
|
emojis: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: EmojiGroupId.Activity,
|
||||||
|
order: 3,
|
||||||
|
emojis: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: EmojiGroupId.Travel,
|
||||||
|
order: 4,
|
||||||
|
emojis: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: EmojiGroupId.Object,
|
||||||
|
order: 5,
|
||||||
|
emojis: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: EmojiGroupId.Symbol,
|
||||||
|
order: 6,
|
||||||
|
emojis: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: EmojiGroupId.Flag,
|
||||||
|
order: 7,
|
||||||
|
emojis: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const emojis: IEmoji[] = [];
|
||||||
|
|
||||||
|
function addEmojiToGroup(groupIndex: number, emoji: IEmoji) {
|
||||||
|
emojiGroups[groupIndex].emojis.push(emoji);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getGroupIndex(emoji: IEmoji): number | undefined {
|
||||||
|
if (emoji.group === 0 || emoji.group === 1) return 0;
|
||||||
|
if (emoji.group === 3) return 1;
|
||||||
|
if (emoji.group === 4) return 2;
|
||||||
|
if (emoji.group === 6) return 3;
|
||||||
|
if (emoji.group === 5) return 4;
|
||||||
|
if (emoji.group === 7) return 5;
|
||||||
|
if (emoji.group === 8 || typeof emoji.group === 'undefined') return 6;
|
||||||
|
if (emoji.group === 9) return 7;
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
emojisData.forEach((emoji) => {
|
||||||
|
const myShortCodes = getShortcodesFor(emoji.hexcode);
|
||||||
|
if (!myShortCodes) return;
|
||||||
|
if (Array.isArray(myShortCodes) && myShortCodes.length === 0) return;
|
||||||
|
|
||||||
|
const em: IEmoji = {
|
||||||
|
...emoji,
|
||||||
|
shortcode: Array.isArray(myShortCodes) ? myShortCodes[0] : myShortCodes,
|
||||||
|
shortcodes: Array.isArray(myShortCodes) ? myShortCodes : emoji.shortcodes,
|
||||||
|
};
|
||||||
|
|
||||||
|
const groupIndex = getGroupIndex(em);
|
||||||
|
if (groupIndex !== undefined) {
|
||||||
|
addEmojiToGroup(groupIndex, em);
|
||||||
|
emojis.push(em);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
@ -1,8 +1,15 @@
|
||||||
import { CompactEmoji, fromUnicodeToHexcode } from 'emojibase';
|
import { CompactEmoji, fromUnicodeToHexcode } from 'emojibase';
|
||||||
import emojisData from 'emojibase-data/en/compact.json';
|
|
||||||
import joypixels from 'emojibase-data/en/shortcodes/joypixels.json';
|
import joypixels from 'emojibase-data/en/shortcodes/joypixels.json';
|
||||||
import emojibase from 'emojibase-data/en/shortcodes/emojibase.json';
|
import emojibase from 'emojibase-data/en/shortcodes/emojibase.json';
|
||||||
|
|
||||||
|
// Light emoji module. Holds the shortcode lookups + types + group enum — the
|
||||||
|
// only emoji surface the broadly-imported HTML parser (and thus the boot
|
||||||
|
// graph, via RoomNavItem/SpaceTabs → InviteUserPrompt → react-custom-html-parser)
|
||||||
|
// actually needs. The heavy `compact.json` dataset (~506 KB) that builds the
|
||||||
|
// `emojis` / `emojiGroups` arrays for the picker + autocomplete lives in
|
||||||
|
// `./emoji-data` so it only loads inside the lazy Room/Settings chunks instead
|
||||||
|
// of the initial bundle. Don't re-import `compact.json` here or the split
|
||||||
|
// regresses.
|
||||||
export type IEmoji = CompactEmoji & {
|
export type IEmoji = CompactEmoji & {
|
||||||
shortcode: string;
|
shortcode: string;
|
||||||
};
|
};
|
||||||
|
|
@ -33,82 +40,3 @@ export const getShortcodeFor = (hexcode: string): string | undefined => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getHexcodeForEmoji = fromUnicodeToHexcode;
|
export const getHexcodeForEmoji = fromUnicodeToHexcode;
|
||||||
|
|
||||||
export const emojiGroups: IEmojiGroup[] = [
|
|
||||||
{
|
|
||||||
id: EmojiGroupId.People,
|
|
||||||
order: 0,
|
|
||||||
emojis: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: EmojiGroupId.Nature,
|
|
||||||
order: 1,
|
|
||||||
emojis: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: EmojiGroupId.Food,
|
|
||||||
order: 2,
|
|
||||||
emojis: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: EmojiGroupId.Activity,
|
|
||||||
order: 3,
|
|
||||||
emojis: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: EmojiGroupId.Travel,
|
|
||||||
order: 4,
|
|
||||||
emojis: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: EmojiGroupId.Object,
|
|
||||||
order: 5,
|
|
||||||
emojis: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: EmojiGroupId.Symbol,
|
|
||||||
order: 6,
|
|
||||||
emojis: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: EmojiGroupId.Flag,
|
|
||||||
order: 7,
|
|
||||||
emojis: [],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const emojis: IEmoji[] = [];
|
|
||||||
|
|
||||||
function addEmojiToGroup(groupIndex: number, emoji: IEmoji) {
|
|
||||||
emojiGroups[groupIndex].emojis.push(emoji);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getGroupIndex(emoji: IEmoji): number | undefined {
|
|
||||||
if (emoji.group === 0 || emoji.group === 1) return 0;
|
|
||||||
if (emoji.group === 3) return 1;
|
|
||||||
if (emoji.group === 4) return 2;
|
|
||||||
if (emoji.group === 6) return 3;
|
|
||||||
if (emoji.group === 5) return 4;
|
|
||||||
if (emoji.group === 7) return 5;
|
|
||||||
if (emoji.group === 8 || typeof emoji.group === 'undefined') return 6;
|
|
||||||
if (emoji.group === 9) return 7;
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
emojisData.forEach((emoji) => {
|
|
||||||
const myShortCodes = getShortcodesFor(emoji.hexcode);
|
|
||||||
if (!myShortCodes) return;
|
|
||||||
if (Array.isArray(myShortCodes) && myShortCodes.length === 0) return;
|
|
||||||
|
|
||||||
const em: IEmoji = {
|
|
||||||
...emoji,
|
|
||||||
shortcode: Array.isArray(myShortCodes) ? myShortCodes[0] : myShortCodes,
|
|
||||||
shortcodes: Array.isArray(myShortCodes) ? myShortCodes : emoji.shortcodes,
|
|
||||||
};
|
|
||||||
|
|
||||||
const groupIndex = getGroupIndex(em);
|
|
||||||
if (groupIndex !== undefined) {
|
|
||||||
addEmojiToGroup(groupIndex, em);
|
|
||||||
emojis.push(em);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { MatrixClient } from 'matrix-js-sdk';
|
import { MatrixClient } from 'matrix-js-sdk';
|
||||||
import { getAccountData } from '../utils/room';
|
import { getAccountData } from '../utils/room';
|
||||||
import { IEmoji, emojis } from './emoji';
|
import { IEmoji } from './emoji';
|
||||||
|
import { emojis } from './emoji-data';
|
||||||
import { AccountDataEvent } from '../../types/matrix/accountData';
|
import { AccountDataEvent } from '../../types/matrix/accountData';
|
||||||
|
|
||||||
type EmojiUnicode = string;
|
type EmojiUnicode = string;
|
||||||
|
|
|
||||||
|
|
@ -306,6 +306,64 @@ export default defineConfig({
|
||||||
copyPublicDir: false,
|
copyPublicDir: false,
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
plugins: [inject({ Buffer: ['buffer', 'Buffer'] })],
|
plugins: [inject({ Buffer: ['buffer', 'Buffer'] })],
|
||||||
|
output: {
|
||||||
|
// Conservative vendor splitting: carve only the large, stable,
|
||||||
|
// leaf-ish dependency families out of the single ~1 MB-gzip app
|
||||||
|
// chunk into their own cacheable chunks. The win is cache stability
|
||||||
|
// — an app-code redeploy no longer busts the matrix-js-sdk / slate
|
||||||
|
// bytes for returning users, and the two load in parallel with the
|
||||||
|
// entry. We deliberately do NOT split react / folds / react-aria:
|
||||||
|
// they are boot-critical and tightly coupled, and aggressive vendor
|
||||||
|
// splitting under topLevelAwait + wasm can reorder init and break
|
||||||
|
// shared singletons (element-web likewise avoids hand-rolled vendor
|
||||||
|
// cacheGroups). Heavy *feature* code is code-split via React.lazy in
|
||||||
|
// Router.tsx instead — that's what actually shrinks first load.
|
||||||
|
manualChunks(id) {
|
||||||
|
// Pin the heavy emoji PICKER dataset (~506 KB `compact.json` + the
|
||||||
|
// `emoji-data.ts` module that processes it) to its own chunk so it
|
||||||
|
// stays OFF the initial bundle — only the lazy Room / Settings chunks
|
||||||
|
// import it (on first room open / picker mount). Runs BEFORE the
|
||||||
|
// node_modules guard so the src module is pinned too.
|
||||||
|
if (id.includes('/plugins/emoji-data') || id.includes('emojibase-data/en/compact')) {
|
||||||
|
return 'emoji-data';
|
||||||
|
}
|
||||||
|
// Pin the LIGHT emoji module (shortcode lookups + `getHexcodeForEmoji`)
|
||||||
|
// and its shortcode maps to a SEPARATE chunk. These are genuinely on the
|
||||||
|
// boot path — the HTML parser (react-custom-html-parser, reachable from
|
||||||
|
// RoomNavItem → InviteUserPrompt) statically imports them. Pinning them
|
||||||
|
// here is what KEEPS `emoji-data` off boot: without it Rollup folds the
|
||||||
|
// light module into the `emoji-data` chunk (it's a dependency of
|
||||||
|
// `emoji-data.ts`), and the entry's boot import of the light functions
|
||||||
|
// then drags the 506 KB `compact.json` into the initial load with it.
|
||||||
|
// `.ts` is matched specifically so this never catches `emoji-data.ts`
|
||||||
|
// (already returned above).
|
||||||
|
if (id.includes('/plugins/emoji.ts') || id.includes('emojibase-data/en/shortcodes')) {
|
||||||
|
return 'emoji-shortcodes';
|
||||||
|
}
|
||||||
|
if (!id.includes('node_modules')) return undefined;
|
||||||
|
// Conservative vendor splitting: carve only the large, stable,
|
||||||
|
// leaf-ish dependency families out of the single app chunk into their
|
||||||
|
// own cacheable chunks. The win is cache stability — an app-code
|
||||||
|
// redeploy no longer busts the matrix-js-sdk / slate bytes for
|
||||||
|
// returning users. We deliberately do NOT split react / folds /
|
||||||
|
// react-aria: they are boot-critical and tightly coupled, and
|
||||||
|
// aggressive vendor splitting under topLevelAwait + wasm can reorder
|
||||||
|
// init and break shared singletons (element-web likewise avoids
|
||||||
|
// hand-rolled vendor cacheGroups).
|
||||||
|
if (
|
||||||
|
id.includes('matrix-js-sdk') ||
|
||||||
|
id.includes('@matrix-org/') ||
|
||||||
|
id.includes('matrix-events-sdk') ||
|
||||||
|
id.includes('matrix-widget-api')
|
||||||
|
) {
|
||||||
|
return 'matrix-sdk';
|
||||||
|
}
|
||||||
|
if (/[\\/]node_modules[\\/](slate|slate-react|slate-dom|slate-history)[\\/]/.test(id)) {
|
||||||
|
return 'editor';
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue