Improve lazy loading (#12393)

Creating manual chunk was a bad idea, we should always solve lazy
loading problem at the source instance.

Setting a 4.5MB for the index bundle size, CI will fail if we go above.

There is still a lot of room for optimizations!
- More agressive lazy loading (e.g. xyflow and tiptap are still loaded
in index!)
- Add a  prefetch mechanism
- Add stronger CI checks to make sure libraries we've set asides are not
added back
- Fix AllIcons component with does not work as intended (loaded on
initial load)
This commit is contained in:
Félix Malfait
2025-06-01 09:33:16 +02:00
committed by GitHub
parent c74d7fe986
commit f6bfec882a
37 changed files with 577 additions and 277 deletions

View File

@ -6,7 +6,6 @@ import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { ResponsiveLine } from '@nivo/line';
import { isNumber } from '@tiptap/core';
import {
QueueMetricsTimeRange,
useGetQueueMetricsQuery,
@ -204,11 +203,12 @@ export const WorkerMetricsGraph = ({
.filter(([key]) => key !== '__typename')
.map(([key, value]) => ({
label: key.charAt(0).toUpperCase() + key.slice(1),
value: isNumber(value)
? value
: Array.isArray(value)
? value.length
: String(value),
value:
typeof value === 'number'
? value
: Array.isArray(value)
? value.length
: String(value),
}))}
gridAutoColumns="1fr 1fr"
labelAlign="left"

View File

@ -7,7 +7,7 @@ import { countryCodeToCallingCode } from '@/settings/data-model/fields/preview/u
import { Select } from '@/ui/input/components/Select';
import { useCountries } from '@/ui/input/components/internal/hooks/useCountries';
import { useLingui } from '@lingui/react/macro';
import { CountryCode } from 'libphonenumber-js';
import type { CountryCode } from 'libphonenumber-js';
import { IconCircleOff, IconComponentProps, IconMap } from 'twenty-ui/display';
import { z } from 'zod';
import { applySimpleQuotesToString } from '~/utils/string/applySimpleQuotesToString';

View File

@ -1,4 +1,3 @@
import dagre from '@dagrejs/dagre';
import { useTheme } from '@emotion/react';
import { Edge, Node } from '@xyflow/react';
import { useEffect } from 'react';
@ -21,81 +20,91 @@ export const SettingsDataModelOverviewEffect = ({
useFilteredObjectMetadataItems();
useEffect(() => {
const g = new dagre.graphlib.Graph();
g.setGraph({ rankdir: 'LR' });
g.setDefaultEdgeLabel(() => ({}));
const loadDagreAndLayout = async () => {
const dagre = await import('@dagrejs/dagre');
const edges: Edge[] = [];
const nodes = [];
let i = 0;
for (const object of items) {
nodes.push({
id: object.namePlural,
width: 220,
height: 100,
position: { x: i * 300, y: 0 },
data: object,
type: 'object',
});
g.setNode(object.namePlural, { width: 220, height: 100 });
const g = new dagre.default.graphlib.Graph();
g.setGraph({ rankdir: 'LR' });
g.setDefaultEdgeLabel(() => ({}));
for (const field of object.fields) {
if (
isDefined(field.relationDefinition) &&
isDefined(
items.find(
(x) => x.id === field.relationDefinition?.targetObjectMetadata.id,
),
)
) {
const sourceObj =
field.relationDefinition?.sourceObjectMetadata.namePlural;
const targetObj =
field.relationDefinition?.targetObjectMetadata.namePlural;
const edges: Edge[] = [];
const nodes: Node[] = [];
let i = 0;
for (const object of items) {
nodes.push({
id: object.namePlural,
width: 220,
height: 100,
position: { x: i * 300, y: 0 },
data: object,
type: 'object',
});
g.setNode(object.namePlural, { width: 220, height: 100 });
edges.push({
id: `${sourceObj}-${targetObj}`,
source: object.namePlural,
sourceHandle: `${field.id}-right`,
target: field.relationDefinition.targetObjectMetadata.namePlural,
targetHandle: `${field.relationDefinition.targetObjectMetadata}-left`,
type: 'smoothstep',
style: {
strokeWidth: 1,
stroke: theme.color.gray,
},
markerEnd: 'marker',
markerStart: 'marker',
data: {
sourceField: field.id,
targetField: field.relationDefinition.targetFieldMetadata.id,
relation: field.relationDefinition.direction,
sourceObject: sourceObj,
targetObject: targetObj,
},
});
if (!isUndefinedOrNull(sourceObj) && !isUndefinedOrNull(targetObj)) {
g.setEdge(sourceObj, targetObj);
for (const field of object.fields) {
if (
isDefined(field.relationDefinition) &&
isDefined(
items.find(
(x) =>
x.id === field.relationDefinition?.targetObjectMetadata.id,
),
)
) {
const sourceObj =
field.relationDefinition?.sourceObjectMetadata.namePlural;
const targetObj =
field.relationDefinition?.targetObjectMetadata.namePlural;
edges.push({
id: `${sourceObj}-${targetObj}`,
source: object.namePlural,
sourceHandle: `${field.id}-right`,
target: field.relationDefinition.targetObjectMetadata.namePlural,
targetHandle: `${field.relationDefinition.targetObjectMetadata}-left`,
type: 'smoothstep',
style: {
strokeWidth: 1,
stroke: theme.color.gray,
},
markerEnd: 'marker',
markerStart: 'marker',
data: {
sourceField: field.id,
targetField: field.relationDefinition.targetFieldMetadata.id,
relation: field.relationDefinition.direction,
sourceObject: sourceObj,
targetObject: targetObj,
},
});
if (
!isUndefinedOrNull(sourceObj) &&
!isUndefinedOrNull(targetObj)
) {
g.setEdge(sourceObj, targetObj);
}
}
}
i++;
}
i++;
}
dagre.layout(g);
dagre.default.layout(g);
nodes.forEach((node) => {
const nodeWithPosition = g.node(node.id);
node.position = {
// We are shifting the dagre node position (anchor=center center) to the top left
// so it matches the React Flow node anchor point (top left).
x: nodeWithPosition.x - node.width / 2,
y: nodeWithPosition.y - node.height / 2,
};
});
nodes.forEach((node) => {
const nodeWithPosition = g.node(node.id);
node.position = {
// We are shifting the dagre node position (anchor=center center) to the top left
// so it matches the React Flow node anchor point (top left).
x: nodeWithPosition.x - (node.width ?? 0) / 2,
y: nodeWithPosition.y - (node.height ?? 0) / 2,
};
});
setNodes(nodes);
setEdges(edges);
setNodes(nodes);
setEdges(edges);
};
loadDagreAndLayout();
}, [items, setEdges, setNodes, theme]);
return <></>;

View File

@ -1,11 +1,11 @@
import { INDEX_FILE_PATH } from '@/serverless-functions/constants/IndexFilePath';
import { getFunctionInputFromSourceCode } from '@/serverless-functions/utils/getFunctionInputFromSourceCode';
import { useGetOneServerlessFunction } from '@/settings/serverless-functions/hooks/useGetOneServerlessFunction';
import { useGetOneServerlessFunctionSourceCode } from '@/settings/serverless-functions/hooks/useGetOneServerlessFunctionSourceCode';
import { Dispatch, SetStateAction, useState } from 'react';
import { FindOneServerlessFunctionSourceCodeQuery } from '~/generated-metadata/graphql';
import { serverlessFunctionTestDataFamilyState } from '@/workflow/states/serverlessFunctionTestDataFamilyState';
import { Dispatch, SetStateAction, useState } from 'react';
import { useRecoilState } from 'recoil';
import { getFunctionInputFromSourceCode } from '@/serverless-functions/utils/getFunctionInputFromSourceCode';
import { INDEX_FILE_PATH } from '@/serverless-functions/constants/IndexFilePath';
import { FindOneServerlessFunctionSourceCodeQuery } from '~/generated-metadata/graphql';
export type ServerlessFunctionNewFormValues = {
name: string;