Skip to content

Commit a4607a0

Browse files
authored
Reduce bundle size & build space (#262)
* Reduce bundle size to only used packages * Remove use of node-sql-parser
1 parent 2d1d7c0 commit a4607a0

File tree

4 files changed

+105
-76
lines changed

4 files changed

+105
-76
lines changed

apps/src/metabase/helpers/parseSql.ts

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,9 @@
1-
import { Parser } from 'node-sql-parser';
2-
import { uniqBy } from 'lodash';
3-
41
export interface TableAndSchema {
52
name: string;
63
schema: string;
74
count?: number;
85
}
96

10-
// using regex version so we don't have to deal with metabase filters syntax
11-
// might be a better idea to just use this one instead of the ridiculous regex
12-
/*export*/ function getTablesFromSql(sql: string): TableAndSchema[] {
13-
const parser = new Parser();
14-
try {
15-
const tableList = parser.tableList(sql);
16-
// returned is {type}::{schemaName}::{tableName} eg. select::public::users
17-
return tableList.map((table: string) => {
18-
const [type, schema, tableName] = table.split('::');
19-
return {
20-
name: tableName,
21-
schema: schema
22-
};
23-
});
24-
} catch (error) {
25-
console.warn('Error parsing SQL (maybe malformed):', sql, error);
26-
return [];
27-
}
28-
}
297
export const removeSurroundingBackticksAndQuotes = (str: string) => {
308
// trim away surrounding backticks and quotes
319
return str.replace(/^[`"]|[`"]$/g, '');

web/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,14 @@
8686
"dotenv": "^16.4.5",
8787
"eslint": "^8.31.0",
8888
"eslint-config-prettier": "^8.8.0",
89+
"eslint-plugin-jest": "^29.0.1",
8990
"eslint-plugin-prettier": "^4.2.1",
9091
"jest": "30.0.0-alpha.5",
9192
"jest-environment-jsdom": "30.0.0-alpha.5",
9293
"jest-html-reporters": "^3.1.7",
9394
"js-tiktoken": "^1.0.12",
9495
"react-dock": "^0.7.0",
96+
"rollup-plugin-visualizer": "^6.0.3",
9597
"serve": "^14.2.3",
9698
"ts-node": "^10.9.2",
9799
"typescript": "5",

web/src/components/common/YAMLEditor.tsx

Lines changed: 83 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,45 @@
1-
import React, {FC, useEffect, useState, useRef, useCallback, useMemo} from 'react';
2-
import * as monaco from "monaco-editor";
3-
import Editor, { loader } from "@monaco-editor/react";
4-
loader.config({ monaco });
5-
import { configureMonacoYaml } from 'monaco-yaml'
6-
import yamlWorker from "./yaml.worker.js?worker";
1+
import React, {FC, useEffect, useState, useRef, useCallback, useMemo, Suspense} from 'react';
2+
import { Box, Spinner } from '@chakra-ui/react';
73

8-
// @ts-ignore
9-
window.MonacoEnvironment = {
10-
getWorker(moduleId: any, label: string) {
11-
switch (label) {
12-
case 'yaml':
13-
return new yamlWorker();
14-
default:
15-
throw new Error(`Unknown label ${label}`);
16-
}
17-
},
4+
// Lazy load Monaco Editor and related modules
5+
const LazyEditor = React.lazy(() => import("@monaco-editor/react"));
6+
7+
// Dynamic imports for Monaco setup
8+
const loadMonaco = async () => {
9+
const [monaco, { configureMonacoYaml }, yamlWorker] = await Promise.all([
10+
import("monaco-editor"),
11+
import('monaco-yaml'),
12+
import("./yaml.worker.js?worker")
13+
]);
14+
return { monaco, configureMonacoYaml, yamlWorker };
1815
};
1916

2017
// Global configuration to prevent multiple setups
2118
let yamlConfigured = false;
2219
let stylesInjected = false;
20+
let monacoLoaded = false;
2321

24-
const configureYamlOnce = (schemaUri?: string) => {
22+
const configureYamlOnce = async (schemaUri?: string) => {
2523
if (yamlConfigured) return;
2624

25+
const { monaco, configureMonacoYaml, yamlWorker } = await loadMonaco();
26+
27+
// Configure Monaco environment
28+
if (!monacoLoaded) {
29+
// @ts-ignore
30+
window.MonacoEnvironment = {
31+
getWorker(moduleId: any, label: string) {
32+
switch (label) {
33+
case 'yaml':
34+
return new yamlWorker.default();
35+
default:
36+
throw new Error(`Unknown label ${label}`);
37+
}
38+
},
39+
};
40+
monacoLoaded = true;
41+
}
42+
2743
const schemas = schemaUri ? [{
2844
uri: schemaUri,
2945
fileMatch: ['*'],
@@ -93,7 +109,7 @@ export const CodeEditor: FC<CodeEditorProps> = (props) => {
93109
debouncedOnChange(value);
94110
}, [debouncedOnChange]);
95111

96-
const onValidate = useCallback((markers: any[]) => {
112+
const onValidate = useCallback(async (markers: any[]) => {
97113
const yamlMarkerErrors = markers.map((marker: any) => marker.message);
98114
setYamlErrors(yamlMarkerErrors);
99115
onValidation(markers.length > 0);
@@ -110,6 +126,8 @@ export const CodeEditor: FC<CodeEditorProps> = (props) => {
110126
const model = editorRef.current.getModel();
111127
if (!model) return;
112128

129+
const { monaco } = await loadMonaco();
130+
113131
const decorations = markers.map((marker: any) => {
114132
const lineLength = model.getLineLength(marker.startLineNumber) || 1;
115133

@@ -139,10 +157,11 @@ export const CodeEditor: FC<CodeEditorProps> = (props) => {
139157
}
140158
}, [onValidation]);
141159

142-
const handleEditorDidMount = useCallback((editor: monaco.editor.IStandaloneCodeEditor) => {
160+
const handleEditorDidMount = useCallback(async (editor: any) => {
143161
editorRef.current = editor;
144162
injectErrorStyles();
145-
}, []);
163+
await configureYamlOnce(schemaUri);
164+
}, [schemaUri]);
146165

147166
// Configure YAML once when component mounts
148167
useEffect(() => {
@@ -162,38 +181,50 @@ export const CodeEditor: FC<CodeEditorProps> = (props) => {
162181

163182
return (
164183
<div style={{border: "1px solid #ccc"}} className={className}>
165-
<Editor
166-
options={{
167-
readOnly: disabled,
168-
lineDecorationsWidth: 5,
169-
lineNumbersMinChars: 0,
170-
glyphMargin: false,
171-
folding: false,
172-
lineNumbers: 'off',
173-
minimap: {
174-
enabled: false
175-
},
176-
fontSize: 11,
177-
// Performance optimizations
178-
wordWrap: 'off',
179-
scrollBeyondLastLine: false,
180-
renderLineHighlight: 'none',
181-
occurrencesHighlight: 'off',
182-
renderControlCharacters: false,
183-
renderWhitespace: 'none',
184-
automaticLayout: true,
185-
// Reduce some visual overhead
186-
hideCursorInOverviewRuler: true,
187-
overviewRulerBorder: false,
188-
}}
189-
width={width}
190-
height={height}
191-
language={language}
192-
value={value}
193-
onValidate={onValidate}
194-
onChange={handleOnChange}
195-
onMount={handleEditorDidMount}
196-
/>
184+
<Suspense fallback={
185+
<Box
186+
display="flex"
187+
alignItems="center"
188+
justifyContent="center"
189+
width={width}
190+
height={height}
191+
>
192+
<Spinner size="md" />
193+
</Box>
194+
}>
195+
<LazyEditor
196+
options={{
197+
readOnly: disabled,
198+
lineDecorationsWidth: 5,
199+
lineNumbersMinChars: 0,
200+
glyphMargin: false,
201+
folding: false,
202+
lineNumbers: 'off',
203+
minimap: {
204+
enabled: false
205+
},
206+
fontSize: 11,
207+
// Performance optimizations
208+
wordWrap: 'off',
209+
scrollBeyondLastLine: false,
210+
renderLineHighlight: 'none',
211+
occurrencesHighlight: 'off',
212+
renderControlCharacters: false,
213+
renderWhitespace: 'none',
214+
automaticLayout: true,
215+
// Reduce some visual overhead
216+
hideCursorInOverviewRuler: true,
217+
overviewRulerBorder: false,
218+
}}
219+
width={width}
220+
height={height}
221+
language={language}
222+
value={value}
223+
onValidate={onValidate}
224+
onChange={handleOnChange}
225+
onMount={handleEditorDidMount}
226+
/>
227+
</Suspense>
197228
</div>
198229
);
199230
};

web/vite.config.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { defineConfig, loadEnv } from 'vite';
22
import react from '@vitejs/plugin-react'
33
import path from 'path'
44
import child_process from 'child_process'
5+
import { visualizer } from 'rollup-plugin-visualizer'
56

67
export default ({ mode }) => {
78
let env = loadEnv(
@@ -14,7 +15,13 @@ export default ({ mode }) => {
1415
env.npm_package_version = process.env.npm_package_version || ''
1516
return defineConfig({
1617
plugins: [
17-
react()
18+
react(),
19+
...(mode === 'production' ? [visualizer({
20+
filename: 'web-build/bundle-analysis.html',
21+
open: false,
22+
gzipSize: true,
23+
brotliSize: true,
24+
})] : [])
1825
],
1926
assetsInclude: ['**/*.md'],
2027
resolve: {
@@ -31,7 +38,18 @@ export default ({ mode }) => {
3138
build: {
3239
outDir: path.resolve(__dirname, './web-build'),
3340
emptyOutDir: true,
34-
sourcemap: true,
41+
sourcemap: mode === 'development',
42+
rollupOptions: {
43+
output: {
44+
manualChunks: {
45+
'monaco-editor': ['monaco-editor', '@monaco-editor/react', 'monaco-yaml'],
46+
'react-vendor': ['react', 'react-dom', 'react-redux', '@reduxjs/toolkit'],
47+
'ui-vendor': ['@chakra-ui/react', '@chakra-ui/icons', '@emotion/react', '@emotion/styled', 'framer-motion'],
48+
'utility-vendor': ['lodash', 'axios', 'query-string'],
49+
'apps-vendor': ['apps']
50+
}
51+
}
52+
}
3553
},
3654
root: path.resolve(__dirname, 'src/app'), // Set the root to your app directory
3755
publicDir: path.resolve(__dirname, 'public'), // Set the public directory to your public directory

0 commit comments

Comments
 (0)