chore: Improve bundle size (#5761)

This commit is contained in:
Bruno Quaresma 2023-01-18 14:31:31 -03:00 committed by GitHub
parent 6ed4e21e8b
commit 2117eb4f31
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 84 additions and 209 deletions

3
.gitignore vendored
View File

@ -36,6 +36,9 @@ cli/testdata/.gen-golden
/dist/
site/out/
# Bundle analysis
site/stats/
*.tfstate
*.tfstate.backup
*.tfplan

View File

@ -39,6 +39,9 @@ cli/testdata/.gen-golden
/dist/
site/out/
# Bundle analysis
site/stats/
*.tfstate
*.tfstate.backup
*.tfplan

View File

@ -39,6 +39,9 @@ playwright-report/*
../dist/
out/
# Bundle analysis
stats/
*.tfstate
*.tfstate.backup
*.tfplan

View File

@ -39,6 +39,9 @@ playwright-report/*
../dist/
out/
# Bundle analysis
stats/
*.tfstate
*.tfstate.backup
*.tfplan

View File

@ -1,7 +1,6 @@
import "@testing-library/jest-dom"
import { cleanup } from "@testing-library/react"
import crypto from "crypto"
import * as util from "util"
import { server } from "./src/testHelpers/server"
import "jest-location-mock"
@ -32,27 +31,5 @@ afterEach(() => {
// Clean up after the tests are finished.
afterAll(() => server.close())
// Helper utility to fail jest tests if a console.error is logged
// Pulled from this blog post:
// https://www.benmvp.com/blog/catch-warnings-jest-tests/
// For now, I limited this to just 'error' - but failing on warnings
// would be a nice next step! We may need to filter out some noise
// from material-ui though.
const CONSOLE_FAIL_TYPES = ["error" /* 'warn' */] as const
// Throw errors when a `console.error` or `console.warn` happens
// by overriding the functions
CONSOLE_FAIL_TYPES.forEach((logType: typeof CONSOLE_FAIL_TYPES[number]) => {
global.console[logType] = <Type>(format: string, ...args: Type[]): void => {
throw new Error(
`Failing due to console.${logType} while running test!\n\n${util.format(
format,
...args,
)}`,
)
}
})
// This is needed because we are compiling under `--isolatedModules`
export {}

View File

@ -24,6 +24,7 @@
"test:coverage": "jest --selectProjects test --collectCoverage",
"test:watch": "jest --selectProjects test --watch",
"typegen": "xstate typegen 'src/**/*.ts'",
"stats": "STATS=true yarn build && npx http-server ./stats -p 8081 -c-1",
"deadcode": "ts-prune | grep -v \".stories\\|.typegen\\|.config\\|e2e\\|__mocks__\\|used in module\\|testHelpers\\|typesGenerated\" || echo \"No deadcode found.\""
},
"dependencies": {
@ -58,6 +59,7 @@
"jest-location-mock": "1.0.9",
"js-untar": "2.0.0",
"just-debounce-it": "3.1.1",
"lodash": "4.17.21",
"playwright": "^1.29.2",
"react": "18.2.0",
"react-chartjs-2": "4.3.1",
@ -69,6 +71,7 @@
"react-router-dom": "6.4.1",
"react-syntax-highlighter": "15.5.0",
"remark-gfm": "3.0.1",
"rollup-plugin-visualizer": "5.9.0",
"sourcemapped-stacktrace": "1.1.11",
"ts-prune": "0.10.3",
"tzdata": "1.0.30",

View File

@ -1,7 +1,7 @@
import Button from "@material-ui/core/Button"
import InputAdornment from "@material-ui/core/InputAdornment"
import Popover from "@material-ui/core/Popover"
import TextField, { TextFieldProps } from "@material-ui/core/TextField"
import TextField from "@material-ui/core/TextField"
import { OpenDropdown } from "components/DropdownArrows/DropdownArrows"
import { useRef, FC, useState } from "react"
import Picker from "@emoji-mart/react"
@ -9,10 +9,9 @@ import { makeStyles } from "@material-ui/core/styles"
import { colors } from "theme/colors"
import { useTranslation } from "react-i18next"
import data from "@emoji-mart/data/sets/14/twitter.json"
import { IconFieldProps } from "./types"
export const IconField: FC<
TextFieldProps & { onPickEmoji: (value: string) => void }
> = ({ onPickEmoji, ...textFieldProps }) => {
const IconField: FC<IconFieldProps> = ({ onPickEmoji, ...textFieldProps }) => {
if (
typeof textFieldProps.value !== "string" &&
typeof textFieldProps.value !== "undefined"
@ -111,3 +110,5 @@ const useStyles = makeStyles((theme) => ({
paddingBottom: theme.spacing(0.5),
},
}))
export default IconField

View File

@ -0,0 +1,12 @@
import { lazy, FC, Suspense } from "react"
import { IconFieldProps } from "./types"
const IconField = lazy(() => import("./IconField"))
export const LazyIconField: FC<IconFieldProps> = (props) => {
return (
<Suspense>
<IconField {...props} />
</Suspense>
)
}

View File

@ -0,0 +1,5 @@
import { TextFieldProps } from "@material-ui/core/TextField"
export type IconFieldProps = TextFieldProps & {
onPickEmoji: (value: string) => void
}

View File

@ -7,7 +7,6 @@ import {
TemplateExample,
} from "api/typesGenerated"
import { FormFooter } from "components/FormFooter/FormFooter"
import { IconField } from "components/IconField/IconField"
import { ParameterInput } from "components/ParameterInput/ParameterInput"
import { Stack } from "components/Stack/Stack"
import {
@ -23,6 +22,7 @@ import { CreateTemplateData } from "xServices/createTemplate/createTemplateXServ
import * as Yup from "yup"
import { WorkspaceBuildLogs } from "components/WorkspaceBuildLogs/WorkspaceBuildLogs"
import { HelpTooltip, HelpTooltipText } from "components/Tooltips/HelpTooltip"
import { LazyIconField } from "components/IconField/LazyIconField"
const validationSchema = Yup.object({
name: nameValidator("Name"),
@ -154,7 +154,7 @@ export const CreateTemplateForm: FC<CreateTemplateFormProps> = ({
variant="outlined"
/>
<IconField
<LazyIconField
{...getFieldHelpers("icon")}
disabled={isSubmitting}
onChange={onChangeTrimmed(form)}

View File

@ -1,21 +1,14 @@
import data from "@emoji-mart/data/sets/14/twitter.json"
import Picker from "@emoji-mart/react"
import Button from "@material-ui/core/Button"
import InputAdornment from "@material-ui/core/InputAdornment"
import Popover from "@material-ui/core/Popover"
import { makeStyles } from "@material-ui/core/styles"
import TextField from "@material-ui/core/TextField"
import { Group } from "api/typesGenerated"
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne"
import { OpenDropdown } from "components/DropdownArrows/DropdownArrows"
import { FormFooter } from "components/FormFooter/FormFooter"
import { FullPageForm } from "components/FullPageForm/FullPageForm"
import { LazyIconField } from "components/IconField/LazyIconField"
import { FullScreenLoader } from "components/Loader/FullScreenLoader"
import { Margins } from "components/Margins/Margins"
import { useFormik } from "formik"
import { useRef, useState, FC } from "react"
import { FC } from "react"
import { useTranslation } from "react-i18next"
import { colors } from "theme/colors"
import { getFormHelpers, nameValidator, onChangeTrimmed } from "util/formUtils"
import * as Yup from "yup"
@ -37,7 +30,6 @@ const UpdateGroupForm: FC<{
onCancel: () => void
isLoading: boolean
}> = ({ group, errors, onSubmit, onCancel, isLoading }) => {
const [isEmojiPickerOpen, setIsEmojiPickerOpen] = useState(false)
const form = useFormik<FormData>({
initialValues: {
name: group.name,
@ -48,9 +40,6 @@ const UpdateGroupForm: FC<{
onSubmit,
})
const getFieldHelpers = getFormHelpers<FormData>(form, errors)
const hasIcon = form.values.avatar_url && form.values.avatar_url !== ""
const emojiButtonRef = useRef<HTMLButtonElement>(null)
const styles = useStyles()
const { t } = useTranslation("common")
return (
@ -65,65 +54,16 @@ const UpdateGroupForm: FC<{
label="Name"
variant="outlined"
/>
<TextField
<LazyIconField
{...getFieldHelpers("avatar_url")}
onChange={onChangeTrimmed(form)}
autoFocus
fullWidth
label="Icon"
label={t("form.fields.icon")}
variant="outlined"
InputProps={{
endAdornment: hasIcon ? (
<InputAdornment position="end">
<img
alt=""
src={form.values.avatar_url}
className={styles.adornment}
// This prevent browser to display the ugly error icon if the
// image path is wrong or user didn't finish typing the url
onError={(e) => (e.currentTarget.style.display = "none")}
onLoad={(e) => (e.currentTarget.style.display = "inline")}
/>
</InputAdornment>
) : undefined,
}}
onPickEmoji={(value) => form.setFieldValue("avatar_url", value)}
/>
<Button
fullWidth
ref={emojiButtonRef}
variant="outlined"
size="small"
endIcon={<OpenDropdown />}
onClick={() => {
setIsEmojiPickerOpen((v) => !v)
}}
>
{t("emojiPicker.select")}
</Button>
<Popover
id="emoji"
open={isEmojiPickerOpen}
anchorEl={emojiButtonRef.current}
onClose={() => {
setIsEmojiPickerOpen(false)
}}
>
<Picker
theme="dark"
data={data}
onEmojiSelect={(emojiData) => {
form
.setFieldValue("avatar_url", `/emojis/${emojiData.unified}.png`)
.catch((ex) => {
console.error(ex)
})
setIsEmojiPickerOpen(false)
}}
/>
</Popover>
<TextField
{...getFieldHelpers("quota_allowance")}
onChange={onChangeTrimmed(form)}
@ -182,21 +122,4 @@ export const SettingsGroupPageView: FC<SettingsGroupPageViewProps> = ({
)
}
const useStyles = makeStyles((theme) => ({
"@global": {
"em-emoji-picker": {
"--rgb-background": theme.palette.background.paper,
"--rgb-input": colors.gray[17],
"--rgb-color": colors.gray[4],
},
},
adornment: {
width: theme.spacing(3),
height: theme.spacing(3),
},
iconField: {
paddingBottom: theme.spacing(0.5),
},
}))
export default SettingsGroupPageView

View File

@ -1,20 +1,12 @@
import Box from "@material-ui/core/Box"
import Checkbox from "@material-ui/core/Checkbox"
import Typography from "@material-ui/core/Typography"
import data from "@emoji-mart/data/sets/14/twitter.json"
import Picker from "@emoji-mart/react"
import Button from "@material-ui/core/Button"
import InputAdornment from "@material-ui/core/InputAdornment"
import Popover from "@material-ui/core/Popover"
import { makeStyles } from "@material-ui/core/styles"
import TextField from "@material-ui/core/TextField"
import { Template, UpdateTemplateMeta } from "api/typesGenerated"
import { OpenDropdown } from "components/DropdownArrows/DropdownArrows"
import { FormFooter } from "components/FormFooter/FormFooter"
import { Stack } from "components/Stack/Stack"
import { FormikContextType, FormikTouched, useFormik } from "formik"
import { FC, useRef, useState } from "react"
import { colors } from "theme/colors"
import { FC } from "react"
import {
getFormHelpers,
nameValidator,
@ -25,6 +17,7 @@ import * as Yup from "yup"
import i18next from "i18next"
import { useTranslation } from "react-i18next"
import { Maybe } from "components/Conditionals/Maybe"
import { LazyIconField } from "components/IconField/LazyIconField"
const TTLHelperText = ({ ttl }: { ttl?: number }) => {
const { t } = useTranslation("templateSettingsPage")
@ -81,7 +74,6 @@ export const TemplateSettingsForm: FC<TemplateSettingsForm> = ({
isSubmitting,
initialTouched,
}) => {
const [isEmojiPickerOpen, setIsEmojiPickerOpen] = useState(false)
const validationSchema = getValidationSchema()
const form: FormikContextType<UpdateTemplateMeta> =
useFormik<UpdateTemplateMeta>({
@ -108,10 +100,6 @@ export const TemplateSettingsForm: FC<TemplateSettingsForm> = ({
initialTouched,
})
const getFieldHelpers = getFormHelpers<UpdateTemplateMeta>(form, error)
const styles = useStyles()
const hasIcon = form.values.icon && form.values.icon !== ""
const emojiButtonRef = useRef<HTMLButtonElement>(null)
const { t } = useTranslation("templateSettingsPage")
return (
@ -145,65 +133,15 @@ export const TemplateSettingsForm: FC<TemplateSettingsForm> = ({
rows={2}
/>
<div className={styles.iconField}>
<TextField
{...getFieldHelpers("icon")}
disabled={isSubmitting}
fullWidth
label={t("iconLabel")}
variant="outlined"
InputProps={{
endAdornment: hasIcon ? (
<InputAdornment position="end">
<img
alt=""
src={form.values.icon}
className={styles.adornment}
// This prevent browser to display the ugly error icon if the
// image path is wrong or user didn't finish typing the url
onError={(e) => (e.currentTarget.style.display = "none")}
onLoad={(e) => (e.currentTarget.style.display = "inline")}
/>
</InputAdornment>
) : undefined,
}}
/>
<Button
fullWidth
ref={emojiButtonRef}
variant="outlined"
size="small"
endIcon={<OpenDropdown />}
onClick={() => {
setIsEmojiPickerOpen((v) => !v)
}}
>
{t("selectEmoji")}
</Button>
<Popover
id="emoji"
open={isEmojiPickerOpen}
anchorEl={emojiButtonRef.current}
onClose={() => {
setIsEmojiPickerOpen(false)
}}
>
<Picker
theme="dark"
data={data}
onEmojiSelect={(emojiData) => {
// See: https://github.com/missive/emoji-mart/issues/51#issuecomment-287353222
form.setFieldValue(
"icon",
`/emojis/${emojiData.unified.replace(/-fe0f$/, "")}.png`,
)
setIsEmojiPickerOpen(false)
}}
/>
</Popover>
</div>
<LazyIconField
{...getFieldHelpers("icon")}
disabled={isSubmitting}
onChange={onChangeTrimmed(form)}
fullWidth
label={t("form.fields.icon")}
variant="outlined"
onPickEmoji={(value) => form.setFieldValue("icon", value)}
/>
<TextField
{...getFieldHelpers(
@ -244,20 +182,3 @@ export const TemplateSettingsForm: FC<TemplateSettingsForm> = ({
</form>
)
}
const useStyles = makeStyles((theme) => ({
"@global": {
"em-emoji-picker": {
"--rgb-background": theme.palette.background.paper,
"--rgb-input": colors.gray[17],
"--rgb-color": colors.gray[4],
},
},
adornment: {
width: theme.spacing(3),
height: theme.spacing(3),
},
iconField: {
paddingBottom: theme.spacing(0.5),
},
}))

View File

@ -26,7 +26,7 @@ const validFormValues = {
name: "Name",
display_name: "A display name",
description: "A description",
icon: "A string",
icon: "vscode.png",
default_ttl_ms: 1,
allow_user_cancel_workspace_jobs: false,
}

View File

@ -3,7 +3,7 @@ import { FieldError } from "api/errors"
import { everyOneGroup } from "util/groups"
import * as Types from "../api/types"
import * as TypesGen from "../api/typesGenerated"
import { range } from "lodash"
import range from "lodash/range"
import { Permissions } from "xServices/auth/authXService"
export const MockTemplateDAUResponse: TypesGen.TemplateDAUsResponse = {

View File

@ -1,9 +1,20 @@
import react from "@vitejs/plugin-react"
import path from "path"
import { defineConfig } from "vite"
import { defineConfig, PluginOption } from "vite"
import { visualizer } from "rollup-plugin-visualizer"
const plugins: PluginOption[] = [react()]
if (process.env.STATS !== undefined) {
plugins.push(
visualizer({
filename: "./stats/index.html",
}),
)
}
export default defineConfig({
plugins: [react()],
plugins: plugins,
publicDir: path.resolve(__dirname, "./static"),
build: {
outDir: path.resolve(__dirname, "./out"),

View File

@ -9549,7 +9549,7 @@ lodash.uniq@4.5.0:
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==
lodash@^4.0.1, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0:
lodash@4.17.21, lodash@^4.0.1, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@ -12226,6 +12226,16 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
hash-base "^3.0.0"
inherits "^2.0.1"
rollup-plugin-visualizer@5.9.0:
version "5.9.0"
resolved "https://registry.yarnpkg.com/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.9.0.tgz#013ac54fb6a9d7c9019e7eb77eced673399e5a0b"
integrity sha512-bbDOv47+Bw4C/cgs0czZqfm8L82xOZssk4ayZjG40y9zbXclNk7YikrZTDao6p7+HDiGxrN0b65SgZiVm9k1Cg==
dependencies:
open "^8.4.0"
picomatch "^2.3.1"
source-map "^0.7.4"
yargs "^17.5.1"
rollup@~2.78.0:
version "2.78.1"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.78.1.tgz#52fe3934d9c83cb4f7c4cb5fb75d88591be8648f"
@ -12680,7 +12690,7 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
source-map@^0.7.3:
source-map@^0.7.3, source-map@^0.7.4:
version "0.7.4"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656"
integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==
@ -14409,7 +14419,7 @@ yargs@^16.2.0:
y18n "^5.0.5"
yargs-parser "^20.2.2"
yargs@^17.3.1:
yargs@^17.3.1, yargs@^17.5.1:
version "17.6.2"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.6.2.tgz#2e23f2944e976339a1ee00f18c77fedee8332541"
integrity sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==