feat: Add code splitting to reduce bundle size (#1285)

This splits our pages to use separate JavaScript bundles. It
initially splits the terminal, which reduces our primary
bundle size by ~400KB.

We should do this for all pages, but that can come in a future
change. This leaves the loading page empty for now, which I
think is fine. None of our pages are large enough that the blank
screen temporarily would be concerning.
This commit is contained in:
Kyle Carberry
2022-05-04 14:24:31 -05:00
committed by GitHub
parent f911c8a781
commit d7f63217f1
4 changed files with 101 additions and 92 deletions

View File

@ -17,49 +17,82 @@ import { SettingsPage } from "./pages/SettingsPage/SettingsPage"
import { CreateWorkspacePage } from "./pages/TemplatesPages/OrganizationPage/TemplatePage/CreateWorkspacePage" import { CreateWorkspacePage } from "./pages/TemplatesPages/OrganizationPage/TemplatePage/CreateWorkspacePage"
import { TemplatePage } from "./pages/TemplatesPages/OrganizationPage/TemplatePage/TemplatePage" import { TemplatePage } from "./pages/TemplatesPages/OrganizationPage/TemplatePage/TemplatePage"
import { TemplatesPage } from "./pages/TemplatesPages/TemplatesPage" import { TemplatesPage } from "./pages/TemplatesPages/TemplatesPage"
import { TerminalPage } from "./pages/TerminalPage/TerminalPage"
import { CreateUserPage } from "./pages/UsersPage/CreateUserPage/CreateUserPage" import { CreateUserPage } from "./pages/UsersPage/CreateUserPage/CreateUserPage"
import { UsersPage } from "./pages/UsersPage/UsersPage" import { UsersPage } from "./pages/UsersPage/UsersPage"
import { WorkspacePage } from "./pages/WorkspacesPage/WorkspacesPage" import { WorkspacePage } from "./pages/WorkspacesPage/WorkspacesPage"
const TerminalPage = React.lazy(() => import("./pages/TerminalPage/TerminalPage"))
export const AppRouter: React.FC = () => ( export const AppRouter: React.FC = () => (
<Routes> <React.Suspense fallback={<></>}>
<Route path="/"> <Routes>
<Route <Route path="/">
index
element={
<RequireAuth>
<IndexPage />
</RequireAuth>
}
/>
<Route path="login" element={<LoginPage />} />
<Route path="healthz" element={<HealthzPage />} />
<Route
path="cli-auth"
element={
<RequireAuth>
<CliAuthenticationPage />
</RequireAuth>
}
/>
<Route path="templates">
<Route <Route
index index
element={ element={
<AuthAndFrame> <RequireAuth>
<TemplatesPage /> <IndexPage />
</AuthAndFrame> </RequireAuth>
} }
/> />
<Route path=":organization/:template">
<Route path="login" element={<LoginPage />} />
<Route path="healthz" element={<HealthzPage />} />
<Route
path="cli-auth"
element={
<RequireAuth>
<CliAuthenticationPage />
</RequireAuth>
}
/>
<Route path="templates">
<Route <Route
index index
element={ element={
<AuthAndFrame> <AuthAndFrame>
<TemplatePage /> <TemplatesPage />
</AuthAndFrame>
}
/>
<Route path=":organization/:template">
<Route
index
element={
<AuthAndFrame>
<TemplatePage />
</AuthAndFrame>
}
/>
<Route
path="create"
element={
<RequireAuth>
<CreateWorkspacePage />
</RequireAuth>
}
/>
</Route>
</Route>
<Route path="workspaces">
<Route
path=":workspace"
element={
<AuthAndFrame>
<WorkspacePage />
</AuthAndFrame>
}
/>
</Route>
<Route path="users">
<Route
index
element={
<AuthAndFrame>
<UsersPage />
</AuthAndFrame> </AuthAndFrame>
} }
/> />
@ -67,83 +100,53 @@ export const AppRouter: React.FC = () => (
path="create" path="create"
element={ element={
<RequireAuth> <RequireAuth>
<CreateWorkspacePage /> <CreateUserPage />
</RequireAuth> </RequireAuth>
} }
/> />
</Route> </Route>
</Route>
<Route path="workspaces">
<Route <Route
path=":workspace" path="orgs"
element={ element={
<AuthAndFrame> <AuthAndFrame>
<WorkspacePage /> <OrgsPage />
</AuthAndFrame>
}
/>
</Route>
<Route path="users">
<Route
index
element={
<AuthAndFrame>
<UsersPage />
</AuthAndFrame> </AuthAndFrame>
} }
/> />
<Route <Route
path="create" path="settings"
element={ element={
<RequireAuth> <AuthAndFrame>
<CreateUserPage /> <SettingsPage />
</RequireAuth> </AuthAndFrame>
} }
/> />
</Route>
<Route
path="orgs"
element={
<AuthAndFrame>
<OrgsPage />
</AuthAndFrame>
}
/>
<Route
path="settings"
element={
<AuthAndFrame>
<SettingsPage />
</AuthAndFrame>
}
/>
<Route path="preferences" element={<PreferencesLayout />}> <Route path="preferences" element={<PreferencesLayout />}>
<Route path="account" element={<AccountPage />} /> <Route path="account" element={<AccountPage />} />
<Route path="security" element={<SecurityPage />} /> <Route path="security" element={<SecurityPage />} />
<Route path="ssh-keys" element={<SSHKeysPage />} /> <Route path="ssh-keys" element={<SSHKeysPage />} />
<Route path="linked-accounts" element={<LinkedAccountsPage />} /> <Route path="linked-accounts" element={<LinkedAccountsPage />} />
</Route>
<Route path=":username">
<Route path=":workspace">
<Route
path="terminal"
element={
<RequireAuth>
<TerminalPage />
</RequireAuth>
}
/>
</Route> </Route>
</Route>
{/* Using path="*"" means "match anything", so this route <Route path=":username">
<Route path=":workspace">
<Route
path="terminal"
element={
<RequireAuth>
<TerminalPage />
</RequireAuth>
}
/>
</Route>
</Route>
{/* Using path="*"" means "match anything", so this route
acts like a catch-all for URLs that we don't have explicit acts like a catch-all for URLs that we don't have explicit
routes for. */} routes for. */}
<Route path="*" element={<NotFoundPage />} /> <Route path="*" element={<NotFoundPage />} />
</Route> </Route>
</Routes> </Routes>
</React.Suspense>
) )

View File

@ -8,7 +8,7 @@ import { TextDecoder, TextEncoder } from "util"
import { ReconnectingPTYRequest } from "../../api/types" import { ReconnectingPTYRequest } from "../../api/types"
import { history, MockWorkspaceAgent, render } from "../../testHelpers" import { history, MockWorkspaceAgent, render } from "../../testHelpers"
import { server } from "../../testHelpers/server" import { server } from "../../testHelpers/server"
import { Language, TerminalPage } from "./TerminalPage" import TerminalPage, { Language } from "./TerminalPage"
Object.defineProperty(window, "matchMedia", { Object.defineProperty(window, "matchMedia", {
writable: true, writable: true,

View File

@ -17,7 +17,7 @@ export const Language = {
websocketErrorMessagePrefix: "WebSocket failed: ", websocketErrorMessagePrefix: "WebSocket failed: ",
} }
export const TerminalPage: React.FC<{ const TerminalPage: React.FC<{
readonly renderer?: XTerm.RendererType readonly renderer?: XTerm.RendererType
}> = ({ renderer }) => { }> = ({ renderer }) => {
const location = useLocation() const location = useLocation()
@ -200,6 +200,8 @@ export const TerminalPage: React.FC<{
) )
} }
export default TerminalPage
const useStyles = makeStyles(() => ({ const useStyles = makeStyles(() => ({
overlay: { overlay: {
position: "absolute", position: "absolute",

View File

@ -1,4 +1,8 @@
{ {
"extends": "./tsconfig.json", "extends": "./tsconfig.json",
"exclude": ["node_modules", "_jest", "**/*.stories.tsx", "**/*.test.tsx"] "exclude": ["node_modules", "_jest", "**/*.stories.tsx", "**/*.test.tsx"],
"compilerOptions": {
// https://github.com/webpack/webpack/issues/5703#issuecomment-357512412
"module": "esnext"
}
} }