mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-19 15:06:20 +00:00
perf(bundle): lazy-load emoji-mart picker (defer ~82 KB gzip from first paint)
@emoji-mart/data (~430 KB / ~82 KB gzip) was pulled into the initial load by three always-mounted views (catalog custom-prefix, nick-icon, chat-input emoji selector) importing it statically. Add LazyEmojiPicker — a Suspense wrapper that dynamically imports the data + Picker on first open — and use it in those three sites. Verified via the build manifest: the entry chunk no longer imports vendor-emoji statically or dynamically; vendor-emoji is now a dynamicImport of src/index.tsx, loaded on first picker open. No behaviour change. typecheck 0, tests 545/545.
This commit is contained in:
@@ -0,0 +1,32 @@
|
|||||||
|
import { ComponentType, FC, lazy, Suspense } from 'react';
|
||||||
|
|
||||||
|
type EmojiPickerProps = Record<string, unknown>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* emoji-mart's data bundle (`@emoji-mart/data`) is ~430 KB (~82 KB gzip) and was
|
||||||
|
* pulled into the initial app bundle by three always-mounted views that import it
|
||||||
|
* statically. The picker itself opens rarely, so we load both the data and the
|
||||||
|
* `<Picker>` component on demand via a dynamic import — deferring that payload out
|
||||||
|
* of first paint. Drop-in for `<Picker data={data} … />` (the `data` prop is
|
||||||
|
* injected here; forward every other prop unchanged).
|
||||||
|
*/
|
||||||
|
const PickerWithData = lazy(async () =>
|
||||||
|
{
|
||||||
|
const [ dataModule, pickerModule ] = await Promise.all([
|
||||||
|
import('@emoji-mart/data'),
|
||||||
|
import('@emoji-mart/react')
|
||||||
|
]);
|
||||||
|
|
||||||
|
const data = (dataModule as { default: unknown }).default;
|
||||||
|
const Picker = (pickerModule as { default: ComponentType<EmojiPickerProps> }).default;
|
||||||
|
|
||||||
|
const Wrapped: ComponentType<EmojiPickerProps> = props => <Picker data={ data } { ...props } />;
|
||||||
|
|
||||||
|
return { default: Wrapped };
|
||||||
|
});
|
||||||
|
|
||||||
|
export const LazyEmojiPicker: FC<EmojiPickerProps> = props => (
|
||||||
|
<Suspense fallback={ <div className="px-2 py-1 text-[11px] text-white/60">…</div> }>
|
||||||
|
<PickerWithData { ...props } />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
@@ -2,8 +2,7 @@ import { PurchasePrefixComposer } from '@nitrots/nitro-renderer';
|
|||||||
import { FC, useEffect, useMemo, useRef, useState } from 'react';
|
import { FC, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { LocalizeText, SanitizeHtml, SendMessageComposer, PRESET_PREFIX_EFFECTS, parsePrefixColors, getPrefixEffectStyle, PREFIX_EFFECT_KEYFRAMES } from '../../../../../api';
|
import { LocalizeText, SanitizeHtml, SendMessageComposer, PRESET_PREFIX_EFFECTS, parsePrefixColors, getPrefixEffectStyle, PREFIX_EFFECT_KEYFRAMES } from '../../../../../api';
|
||||||
import { CatalogLayoutProps } from './CatalogLayout.types';
|
import { CatalogLayoutProps } from './CatalogLayout.types';
|
||||||
import data from '@emoji-mart/data';
|
import { LazyEmojiPicker } from '../../../../../common/LazyEmojiPicker';
|
||||||
import Picker from '@emoji-mart/react';
|
|
||||||
|
|
||||||
const PRESET_COLORS: string[] = [
|
const PRESET_COLORS: string[] = [
|
||||||
'#FF0000', '#FF6600', '#FFCC00', '#33CC00', '#00CCFF',
|
'#FF0000', '#FF6600', '#FFCC00', '#33CC00', '#00CCFF',
|
||||||
@@ -222,8 +221,7 @@ export const CatalogLayoutCustomPrefixView: FC<CatalogLayoutProps> = props =>
|
|||||||
<>
|
<>
|
||||||
<div className="fixed inset-0" style={ { zIndex: 999, background: 'rgba(0,0,0,0.5)' } } onClick={ () => setShowIconPicker(false) } />
|
<div className="fixed inset-0" style={ { zIndex: 999, background: 'rgba(0,0,0,0.5)' } } onClick={ () => setShowIconPicker(false) } />
|
||||||
<div className="fixed rounded-xl overflow-hidden" style={ { zIndex: 1000, top: '50%', left: '50%', transform: 'translate(-50%, -50%)', boxShadow: '0 8px 32px rgba(0,0,0,0.6)' } }>
|
<div className="fixed rounded-xl overflow-hidden" style={ { zIndex: 1000, top: '50%', left: '50%', transform: 'translate(-50%, -50%)', boxShadow: '0 8px 32px rgba(0,0,0,0.6)' } }>
|
||||||
<Picker
|
<LazyEmojiPicker
|
||||||
data={ data }
|
|
||||||
locale="it"
|
locale="it"
|
||||||
onEmojiSelect={ (emoji: { native: string }) =>
|
onEmojiSelect={ (emoji: { native: string }) =>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { AddLinkEventTracker, CustomPrefixPurchaseFailedEvent, ILinkEventTracker, PurchaseCatalogPrefixComposer, PurchaseNickIconComposer, PurchasePrefixComposer, RemoveLinkEventTracker, RequestNickIconsComposer, SetActiveNickIconComposer, SetActivePrefixComposer, SetDisplayOrderComposer, UserNickIconsEvent } from '@nitrots/nitro-renderer';
|
import { AddLinkEventTracker, CustomPrefixPurchaseFailedEvent, ILinkEventTracker, PurchaseCatalogPrefixComposer, PurchaseNickIconComposer, PurchasePrefixComposer, RemoveLinkEventTracker, RequestNickIconsComposer, SetActiveNickIconComposer, SetActivePrefixComposer, SetDisplayOrderComposer, UserNickIconsEvent } from '@nitrots/nitro-renderer';
|
||||||
import data from '@emoji-mart/data';
|
import { LazyEmojiPicker } from '../../common/LazyEmojiPicker';
|
||||||
import Picker from '@emoji-mart/react';
|
|
||||||
import { FC, useEffect, useMemo, useState } from 'react';
|
import { FC, useEffect, useMemo, useState } from 'react';
|
||||||
import { INickIconItem, IPrefixItem, PRESET_PREFIX_EFFECTS, PRESET_PREFIX_FONTS, SendMessageComposer, getPrefixEffectStyle, getPrefixFontStyle, parsePrefixColors } from '../../api';
|
import { INickIconItem, IPrefixItem, PRESET_PREFIX_EFFECTS, PRESET_PREFIX_FONTS, SendMessageComposer, getPrefixEffectStyle, getPrefixFontStyle, parsePrefixColors } from '../../api';
|
||||||
import { GetNickIconUrl } from '../../assets/images/user_custom/nick_icons';
|
import { GetNickIconUrl } from '../../assets/images/user_custom/nick_icons';
|
||||||
@@ -575,8 +574,7 @@ export const CustomizeNickIconView: FC<{}> = () =>
|
|||||||
<>
|
<>
|
||||||
<div className="fixed inset-0 z-[999]" onClick={ () => setShowEmojiPicker(false) } />
|
<div className="fixed inset-0 z-[999]" onClick={ () => setShowEmojiPicker(false) } />
|
||||||
<div className="fixed left-1/2 top-1/2 z-[1000] -translate-x-1/2 -translate-y-1/2 overflow-hidden rounded-xl shadow-2xl">
|
<div className="fixed left-1/2 top-1/2 z-[1000] -translate-x-1/2 -translate-y-1/2 overflow-hidden rounded-xl shadow-2xl">
|
||||||
<Picker
|
<LazyEmojiPicker
|
||||||
data={ data }
|
|
||||||
locale="en"
|
locale="en"
|
||||||
onEmojiSelect={ (emoji: { native: string }) =>
|
onEmojiSelect={ (emoji: { native: string }) =>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import data from '@emoji-mart/data';
|
|
||||||
import Picker from '@emoji-mart/react';
|
|
||||||
import * as Popover from '@radix-ui/react-popover';
|
import * as Popover from '@radix-ui/react-popover';
|
||||||
|
import { LazyEmojiPicker } from '../../../../common/LazyEmojiPicker';
|
||||||
import { FC, useState } from 'react';
|
import { FC, useState } from 'react';
|
||||||
|
|
||||||
interface ChatInputEmojiSelectorViewProps
|
interface ChatInputEmojiSelectorViewProps
|
||||||
@@ -26,7 +25,7 @@ export const ChatInputEmojiSelectorView: FC<ChatInputEmojiSelectorViewProps> = p
|
|||||||
</Popover.Trigger>
|
</Popover.Trigger>
|
||||||
<Popover.Portal>
|
<Popover.Portal>
|
||||||
<Popover.Content className="z-[1070]" side="top" sideOffset={ 8 }>
|
<Popover.Content className="z-[1070]" side="top" sideOffset={ 8 }>
|
||||||
<Picker data={ data } onEmojiSelect={ handleEmojiSelect } />
|
<LazyEmojiPicker onEmojiSelect={ handleEmojiSelect } />
|
||||||
</Popover.Content>
|
</Popover.Content>
|
||||||
</Popover.Portal>
|
</Popover.Portal>
|
||||||
</Popover.Root>
|
</Popover.Root>
|
||||||
|
|||||||
Reference in New Issue
Block a user