Improve lazy loading (#12186)

WIP, using preview app to test performance (which was very bad)
This commit is contained in:
Félix Malfait
2025-05-22 15:07:01 +02:00
committed by GitHub
parent 4ac47c2a1b
commit 79b4b3f783
26 changed files with 697 additions and 104 deletions

View File

@ -3,9 +3,9 @@
"private": true,
"type": "module",
"scripts": {
"build": "VITE_DISABLE_TYPESCRIPT_CHECKER=true VITE_DISABLE_ESLINT_CHECKER=true NODE_OPTIONS=--max-old-space-size=4500 npx vite build && sh ./scripts/inject-runtime-env.sh",
"build:sourcemaps": "VITE_BUILD_SOURCEMAP=true VITE_DISABLE_TYPESCRIPT_CHECKER=true VITE_DISABLE_ESLINT_CHECKER=true NODE_OPTIONS=--max-old-space-size=7000 npx vite build && sh ./scripts/inject-runtime-env.sh",
"start:prod": "NODE_ENV=production npx vite --host",
"build": "VITE_DISABLE_TYPESCRIPT_CHECKER=true VITE_DISABLE_ESLINT_CHECKER=true NODE_OPTIONS=--max-old-space-size=8192 npx vite build && sh ./scripts/inject-runtime-env.sh",
"build:sourcemaps": "VITE_BUILD_SOURCEMAP=true VITE_DISABLE_TYPESCRIPT_CHECKER=true VITE_DISABLE_ESLINT_CHECKER=true NODE_OPTIONS=--max-old-space-size=8192 npx vite build && sh ./scripts/inject-runtime-env.sh",
"start:prod": "NODE_ENV=production npx serve -s build",
"tsup": "npx tsup"
},
"engines": {
@ -82,6 +82,7 @@
"eslint-plugin-storybook": "^0.6.15",
"eslint-plugin-unicorn": "^51.0.1",
"eslint-plugin-unused-imports": "^3.0.0",
"optionator": "^0.9.1"
"optionator": "^0.9.1",
"rollup-plugin-visualizer": "^5.14.0"
}
}

View File

@ -3,7 +3,7 @@ import {
PDFExporter,
pdfDefaultSchemaMappings,
} from '@blocknote/xl-pdf-exporter';
import * as ReactPDF from '@react-pdf/renderer';
import { pdf } from '@react-pdf/renderer';
import { saveAs } from 'file-saver';
export const exportBlockNoteEditorToPdf = async (
@ -14,6 +14,6 @@ export const exportBlockNoteEditorToPdf = async (
const pdfDocument = await exporter.toReactPDFDocument(editor.document);
const blob = await ReactPDF.pdf(pdfDocument).toBlob();
const blob = await pdf(pdfDocument).toBlob();
saveAs(blob, `${filename}.pdf`);
};

View File

@ -1,7 +1,6 @@
import { ActivityRow } from '@/activities/components/ActivityRow';
import { AttachmentDropdown } from '@/activities/files/components/AttachmentDropdown';
import { AttachmentIcon } from '@/activities/files/components/AttachmentIcon';
import { PREVIEWABLE_EXTENSIONS } from '@/activities/files/components/DocumentViewer';
import { Attachment } from '@/activities/files/types/Attachment';
import { downloadFile } from '@/activities/files/utils/downloadFile';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
@ -17,10 +16,11 @@ import styled from '@emotion/styled';
import { useState } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { formatToHumanReadableDate } from '~/utils/date-utils';
import { getFileNameAndExtension } from '~/utils/file/getFileNameAndExtension';
import { PREVIEWABLE_EXTENSIONS } from '@/activities/files/const/previewable-extensions.const';
import { IconCalendar, OverflowingTextWithTooltip } from 'twenty-ui/display';
import { isModifiedEvent } from 'twenty-ui/utilities';
import { formatToHumanReadableDate } from '~/utils/date-utils';
import { getFileNameAndExtension } from '~/utils/file/getFileNameAndExtension';
const StyledLeftContent = styled.div`
align-items: center;

View File

@ -1,3 +1,4 @@
import { PREVIEWABLE_EXTENSIONS } from '@/activities/files/const/previewable-extensions.const';
import { fetchCsvPreview } from '@/activities/files/utils/fetchCsvPreview';
import DocViewer, { DocViewerRenderers } from '@cyntler/react-doc-viewer';
import '@cyntler/react-doc-viewer/dist/index.css';
@ -41,29 +42,6 @@ type DocumentViewerProps = {
documentUrl: string;
};
export const PREVIEWABLE_EXTENSIONS = [
'bmp',
'csv',
'odt',
'doc',
'docx',
'gif',
'htm',
'html',
'jpg',
'jpeg',
'pdf',
'png',
'ppt',
'pptx',
'tiff',
'txt',
'xls',
'xlsx',
'mp4',
'webp',
];
const MIME_TYPE_MAPPING: Record<
(typeof PREVIEWABLE_EXTENSIONS)[number],
string

View File

@ -0,0 +1,22 @@
export const PREVIEWABLE_EXTENSIONS = [
'bmp',
'csv',
'odt',
'doc',
'docx',
'gif',
'htm',
'html',
'jpg',
'jpeg',
'pdf',
'png',
'ppt',
'pptx',
'tiff',
'txt',
'xls',
'xlsx',
'mp4',
'webp',
];

View File

@ -1,5 +1,5 @@
import { AppErrorBoundaryEffect } from '@/error-handler/components/internal/AppErrorBoundaryEffect';
import * as Sentry from '@sentry/react';
import { captureException } from '@sentry/react';
import { ErrorInfo, ReactNode } from 'react';
import { ErrorBoundary, FallbackProps } from 'react-error-boundary';
@ -15,7 +15,7 @@ export const AppErrorBoundary = ({
resetOnLocationChange = true,
}: AppErrorBoundaryProps) => {
const handleError = (error: Error, info: ErrorInfo) => {
Sentry.captureException(error, (scope) => {
captureException(error, (scope) => {
scope.setExtras({ info });
return scope;
});

View File

@ -1,4 +1,9 @@
import * as Sentry from '@sentry/react';
import {
browserTracingIntegration,
init,
replayIntegration,
setUser,
} from '@sentry/react';
import { isNonEmptyString } from '@sniptt/guards';
import { useEffect, useState } from 'react';
import { useRecoilValue } from 'recoil';
@ -7,8 +12,8 @@ import { currentUserState } from '@/auth/states/currentUserState';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { sentryConfigState } from '@/client-config/states/sentryConfigState';
import { REACT_APP_SERVER_BASE_URL } from '~/config';
import { isDefined } from 'twenty-shared/utils';
import { REACT_APP_SERVER_BASE_URL } from '~/config';
export const SentryInitEffect = () => {
const sentryConfig = useRecoilValue(sentryConfigState);
@ -21,14 +26,11 @@ export const SentryInitEffect = () => {
useEffect(() => {
if (isNonEmptyString(sentryConfig?.dsn) && !isSentryInitialized) {
Sentry.init({
init({
environment: sentryConfig?.environment ?? undefined,
release: sentryConfig?.release ?? undefined,
dsn: sentryConfig?.dsn,
integrations: [
Sentry.browserTracingIntegration({}),
Sentry.replayIntegration(),
],
integrations: [browserTracingIntegration({}), replayIntegration()],
tracePropagationTargets: ['localhost:3001', REACT_APP_SERVER_BASE_URL],
tracesSampleRate: 1.0,
replaysSessionSampleRate: 0.1,
@ -39,14 +41,14 @@ export const SentryInitEffect = () => {
}
if (isDefined(currentUser)) {
Sentry.setUser({
setUser({
email: currentUser?.email,
id: currentUser?.id,
workspaceId: currentWorkspace?.id,
workspaceMemberId: currentWorkspaceMember?.id,
});
} else {
Sentry.setUser(null);
setUser(null);
}
}, [
sentryConfig,

View File

@ -3,7 +3,7 @@ import { ZodHelperLiteral } from '@/object-record/record-field/types/ZodHelperLi
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { ConnectedAccountProvider } from 'twenty-shared/types';
import { ThemeColor } from 'twenty-ui/theme';
import * as z from 'zod';
import { z } from 'zod';
import { RelationDefinitionType } from '~/generated-metadata/graphql';
import { CurrencyCode } from './CurrencyCode';

View File

@ -31,29 +31,24 @@ const StyledBaseContainer = styled.div<{
}
&:hover {
${({
isReadOnly,
fontColorMedium,
backgroundColorSecondary,
fontColorSecondary,
}) =>
isReadOnly
? `
outline: 1px solid ${fontColorMedium};
border-radius: 0px;
background-color: ${backgroundColorSecondary};
color: ${fontColorSecondary};
svg {
color: ${fontColorSecondary};
}
img {
opacity: 0.64;
}
`
: ''}
${(props) => {
if (!props.isReadOnly) return '';
return `
outline: 1px solid ${props.fontColorMedium};
border-radius: 0px;
background-color: ${props.backgroundColorSecondary};
color: ${props.fontColorSecondary};
svg {
color: ${props.fontColorSecondary};
}
img {
opacity: 0.64;
}
`;
}}
}
`;

View File

@ -1,5 +1,5 @@
import styled from '@emotion/styled';
import { Point } from '@nivo/line';
import type { Point } from '@nivo/line';
import { ReactElement } from 'react';
const StyledTooltipContainer = styled.div`

View File

@ -1,7 +1,7 @@
import styled from '@emotion/styled';
import { useState } from 'react';
import { useDropzone } from 'react-dropzone';
import * as XLSX from 'xlsx-ugnis';
import { read, WorkBook } from 'xlsx-ugnis';
import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpreadsheetImportInternal';
import { readFileAsync } from '@/spreadsheet-import/utils/readFilesAsync';
@ -84,7 +84,7 @@ const StyledText = styled.span`
`;
type DropZoneProps = {
onContinue: (data: XLSX.WorkBook, file: File) => void;
onContinue: (data: WorkBook, file: File) => void;
isLoading: boolean;
};
@ -119,7 +119,7 @@ export const DropZone = ({ onContinue, isLoading }: DropZoneProps) => {
onDropAccepted: async ([file]) => {
setLoading(true);
const arrayBuffer = await readFileAsync(file);
const workbook = XLSX.read(arrayBuffer, {
const workbook = read(arrayBuffer, {
cellDates: true,
codepage: 65001, // UTF-8 codepage
dateNF: dateFormat,

View File

@ -1,10 +1,10 @@
import * as XLSX from 'xlsx-ugnis';
import { utils } from 'xlsx-ugnis';
import { mapWorkbook } from '@/spreadsheet-import/utils/mapWorkbook';
describe('mapWorkbook', () => {
it('should map the workbook to a 2D array of strings', () => {
const inputWorkbook = XLSX.utils.book_new();
const inputWorkbook = utils.book_new();
const inputSheetData = [
['Name', 'Age'],
['John', '30'],
@ -12,8 +12,8 @@ describe('mapWorkbook', () => {
];
const expectedOutput = inputSheetData;
const worksheet = XLSX.utils.aoa_to_sheet(inputSheetData);
XLSX.utils.book_append_sheet(inputWorkbook, worksheet, 'Sheet1');
const worksheet = utils.aoa_to_sheet(inputSheetData);
utils.book_append_sheet(inputWorkbook, worksheet, 'Sheet1');
const result = mapWorkbook(inputWorkbook);
@ -21,7 +21,7 @@ describe('mapWorkbook', () => {
});
it('should map the specified sheet of the workbook to a 2D array of strings', () => {
const inputWorkbook = XLSX.utils.book_new();
const inputWorkbook = utils.book_new();
const inputSheet1Data = [
['Name', 'Age'],
['John', '30'],
@ -34,10 +34,10 @@ describe('mapWorkbook', () => {
];
const expectedOutput = inputSheet2Data;
const worksheet1 = XLSX.utils.aoa_to_sheet(inputSheet1Data);
const worksheet2 = XLSX.utils.aoa_to_sheet(inputSheet2Data);
XLSX.utils.book_append_sheet(inputWorkbook, worksheet1, 'Sheet1');
XLSX.utils.book_append_sheet(inputWorkbook, worksheet2, 'Sheet2');
const worksheet1 = utils.aoa_to_sheet(inputSheet1Data);
const worksheet2 = utils.aoa_to_sheet(inputSheet2Data);
utils.book_append_sheet(inputWorkbook, worksheet1, 'Sheet1');
utils.book_append_sheet(inputWorkbook, worksheet2, 'Sheet2');
const result = mapWorkbook(inputWorkbook, 'Sheet2');

View File

@ -1,8 +1,8 @@
import * as XLSX from 'xlsx-ugnis';
import { utils, WorkBook } from 'xlsx-ugnis';
export const mapWorkbook = (workbook: XLSX.WorkBook, sheetName?: string) => {
export const mapWorkbook = (workbook: WorkBook, sheetName?: string) => {
const worksheet = workbook.Sheets[sheetName || workbook.SheetNames[0]];
const data = XLSX.utils.sheet_to_json(worksheet, {
const data = utils.sheet_to_json(worksheet, {
header: 1,
blankrows: false,
raw: false,

View File

@ -1,7 +1,7 @@
import { useMemo } from 'react';
import { hasFlag } from 'country-flag-icons';
import * as Flags from 'country-flag-icons/react/3x2';
import { getCountries, getCountryCallingCode } from 'libphonenumber-js';
import { useMemo } from 'react';
import { Country } from '@/ui/input/components/internal/types/Country';

View File

@ -1,9 +1,9 @@
import * as Flags from 'country-flag-icons/react/3x2';
import { FlagComponent } from 'country-flag-icons/react/3x2';
import { CountryCallingCode, CountryCode } from 'libphonenumber-js';
export type Country = {
countryCode: CountryCode;
countryName: string;
callingCode: CountryCallingCode;
Flag: Flags.FlagComponent;
Flag: FlagComponent;
};

View File

@ -5,8 +5,7 @@ import { LayoutCard } from '@/ui/layout/tab/types/LayoutCard';
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
import styled from '@emotion/styled';
import * as React from 'react';
import { useEffect } from 'react';
import React, { useEffect } from 'react';
import { IconComponent } from 'twenty-ui/display';
import { Tab } from './Tab';

View File

@ -5,10 +5,10 @@ import { WorkflowDiagramNodeVariant } from '@/workflow/workflow-diagram/types/Wo
import { fn } from '@storybook/test';
import '@xyflow/react/dist/style.css';
import { ComponentProps } from 'react';
import { CatalogDecorator, CatalogStory } from 'twenty-ui/testing';
import { ReactflowDecorator } from '~/testing/decorators/ReactflowDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks';
import { WorkflowDiagramStepNodeEditableContent } from '../WorkflowDiagramStepNodeEditableContent';
import { CatalogDecorator, CatalogStory } from 'twenty-ui/testing';
type ComponentState = 'default' | 'hover' | 'selected';

View File

@ -8,9 +8,9 @@ import {
StepOutputSchema,
} from '@/workflow/workflow-variables/types/StepOutputSchema';
import { filterOutputSchema } from '@/workflow/workflow-variables/utils/filterOutputSchema';
import { isEmptyObject } from '@tiptap/core';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { isEmptyObject } from '~/utils/isEmptyObject';
export const useAvailableVariablesInWorkflowStep = ({
objectNameSingularToSelect,

View File

@ -1,4 +1,4 @@
import { JSONContent } from '@tiptap/react';
import type { JSONContent } from '@tiptap/react';
import { parseEditorContent } from '../parseEditorContent';
describe('parseEditorContent', () => {

View File

@ -1,6 +1,6 @@
import { isStandaloneVariableString } from '@/workflow/utils/isStandaloneVariableString';
import { isNonEmptyString } from '@sniptt/guards';
import { JSONContent } from '@tiptap/react';
import type { JSONContent } from '@tiptap/react';
export const CAPTURE_VARIABLE_TAG_REGEX = /({{[^{}]+}})/;

View File

@ -1,4 +1,4 @@
import { JSONContent } from '@tiptap/react';
import type { JSONContent } from '@tiptap/react';
import { isDefined } from 'twenty-shared/utils';
export const parseEditorContent = (json: JSONContent): string => {

View File

@ -5,11 +5,11 @@ import react from '@vitejs/plugin-react-swc';
import wyw from '@wyw-in-js/vite';
import fs from 'fs';
import path from 'path';
import { defineConfig, loadEnv, searchForWorkspaceRoot } from 'vite';
import { visualizer } from 'rollup-plugin-visualizer';
import { defineConfig, loadEnv, PluginOption, searchForWorkspaceRoot } from 'vite';
import checker from 'vite-plugin-checker';
import svgr from 'vite-plugin-svgr';
import tsconfigPaths from 'vite-tsconfig-paths';
type Checkers = Parameters<typeof checker>[0];
export default defineConfig(({ command, mode }) => {
@ -145,6 +145,12 @@ export default defineConfig(({ command, mode }) => {
presets: ['@babel/preset-typescript', '@babel/preset-react'],
},
}),
visualizer({
open: true,
gzipSize: true,
brotliSize: true,
filename: 'dist/stats.html',
}) as PluginOption, // https://github.com/btd/rollup-plugin-visualizer/issues/162#issuecomment-1538265997,
],
optimizeDeps: {
@ -156,7 +162,7 @@ export default defineConfig(({ command, mode }) => {
},
build: {
minify: false,
minify: 'esbuild',
outDir: 'build',
sourcemap: VITE_BUILD_SOURCEMAP === 'true',
rollupOptions: {

View File

@ -98,11 +98,18 @@ export type CatalogOptions = {
export const CatalogDecorator: Decorator = (Story, context) => {
const {
catalog: { dimensions, options },
} = context.parameters;
catalog: { dimensions = [], options = {} } = {
dimensions: [],
options: {},
},
} = context.parameters || {};
if (!dimensions || !Array.isArray(dimensions)) {
return <Story />;
}
const [
dimension1,
dimension1 = emptyDimension,
dimension2 = emptyDimension,
dimension3 = emptyDimension,
dimension4 = emptyDimension,

View File

@ -100,7 +100,7 @@ export default defineConfig(({ command }) => {
// See: https://vitejs.dev/guide/build.html#library-mode
build: {
cssCodeSplit: false,
minify: false,
minify: 'esbuild',
sourcemap: false,
outDir: './dist',
reportCompressedSize: true,