Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core): specific error when dev server is stopped #7476

Merged
merged 17 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
a5a2247
feat(core): when dev server stops, highlight this with own error boun…
jordanl17 Sep 7, 2024
d350222
feat(core): simplified, and using nested error boundary for catching …
jordanl17 Sep 7, 2024
0bba005
refactor(core): simplifying use of DevServerStatus as lazy import
jordanl17 Sep 7, 2024
215a9df
refactor(core): naming refactor
jordanl17 Sep 7, 2024
cf8e8f7
feat(core): removing dev server stopped toast as unnecessary
jordanl17 Sep 9, 2024
cd5ed7e
refactor(core): using built ins from vite; making more explicit this …
jordanl17 Sep 9, 2024
67eaaf5
Merge branch 'next' into sdx-1576-dev-server-stop-error-simplified
jordanl17 Sep 10, 2024
901efe2
Merge branch 'next' into sdx-1576-dev-server-stop-error-simplified
jordanl17 Sep 10, 2024
bdaca08
Merge branch 'next' into sdx-1576-dev-server-stop-error-simplified
jordanl17 Sep 13, 2024
4bb2afc
Merge branch 'next' into sdx-1576-dev-server-stop-error-simplified
jordanl17 Sep 25, 2024
1cbdb3f
Merge branch 'next' into sdx-1576-dev-server-stop-error-simplified
jordanl17 Sep 25, 2024
880b442
chore: moving babel vite transform to test-config package
jordanl17 Sep 25, 2024
eac8bc3
chore: moving babel deps to test-config
jordanl17 Sep 25, 2024
3fb626a
chore: tidy to lock and named export for type used
jordanl17 Sep 25, 2024
6e54679
fix: ignoring un-identified uses of babel presets and plugins in test…
jordanl17 Sep 25, 2024
aa4d272
Merge branch 'next' into sdx-1576-dev-server-stop-error-simplified
jordanl17 Sep 26, 2024
b1bb8cf
chore: naming of vite dev server stopped error
jordanl17 Sep 30, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,6 @@
},
"prettier": "@sanity/prettier-config",
"devDependencies": {
"@babel/preset-env": "^7.24.7",
"@babel/preset-react": "^7.24.7",
"@babel/preset-typescript": "^7.24.7",
"@google-cloud/storage": "^7.11.0",
"@playwright/test": "1.44.1",
"@repo/dev-aliases": "workspace:*",
Expand Down
8 changes: 8 additions & 0 deletions packages/@repo/test-config/.depcheckrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"ignores": [
"@babel/preset-env",
"@babel/preset-react",
"@babel/preset-typescript",
"babel-plugin-transform-vite-meta-hot"
]
}
7 changes: 4 additions & 3 deletions packages/@repo/test-config/jest/createJestConfig.mjs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
/* eslint-disable tsdoc/syntax */

import devAliases from '@repo/dev-aliases'

import path from 'node:path'

import devAliases from '@repo/dev-aliases'
import {escapeRegExp, omit} from 'lodash-es'

import {resolveDirName} from './resolveDirName.mjs'

const dirname = resolveDirName(import.meta.url)
Expand Down Expand Up @@ -71,7 +72,6 @@ export function createJestConfig(config = {}) {
resolver: path.resolve(dirname, './resolver.cjs'),
testEnvironment: path.resolve(dirname, './jsdom.jest.env.ts'),
setupFiles: [...setupFiles, path.resolve(dirname, './setup.ts')],
// testEnvironment: 'jsdom',
testEnvironmentOptions: {
url: 'http://localhost:3333',
},
Expand Down Expand Up @@ -103,6 +103,7 @@ export function createJestConfig(config = {}) {
'@babel/preset-typescript',
['@babel/preset-react', {runtime: 'automatic'}],
],
plugins: ['babel-plugin-transform-vite-meta-hot'],
},
],
},
Expand Down
4 changes: 4 additions & 0 deletions packages/@repo/test-config/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@
"./vitest": "./vitest/index.mjs"
},
"devDependencies": {
"@babel/preset-env": "^7.24.7",
"@babel/preset-react": "^7.24.7",
"@babel/preset-typescript": "^7.24.7",
"@jest/globals": "^29.7.0",
"@repo/dev-aliases": "workspace:*",
"babel-plugin-transform-vite-meta-hot": "^1.0.0",
"dotenv": "^16.4.5",
"jest-environment-jsdom": "^29.7.0",
"lodash-es": "^4.17.21",
Expand Down
4 changes: 4 additions & 0 deletions packages/sanity/src/core/error/ErrorLogger.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,5 +64,9 @@ function isKnownError(err: Error): boolean {
return true
}

if ('ViteDevServerStoppedError' in err && err.ViteDevServerStoppedError) {
return true
}

return false
}
24 changes: 23 additions & 1 deletion packages/sanity/src/core/studio/StudioErrorBoundary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,14 @@ import {
Stack,
Text,
} from '@sanity/ui'
import {type ComponentType, type ErrorInfo, type ReactNode, useCallback, useState} from 'react'
import {
type ComponentType,
type ErrorInfo,
lazy,
type ReactNode,
useCallback,
useState,
} from 'react'
import {ErrorActions, isDev, isProd} from 'sanity'
import {styled} from 'styled-components'
import {useHotModuleReload} from 'use-hot-module-reload'
Expand All @@ -22,6 +29,17 @@ import {CorsOriginError} from '../store'
import {isRecord} from '../util'
import {CorsOriginErrorScreen, SchemaErrorsScreen} from './screens'

/**
juice49 marked this conversation as resolved.
Show resolved Hide resolved
* The DevServerStoppedErrorScreen will always have been lazy loaded to client
* in instances where it is used, since DevServerStoppedError is only thrown
* when this module is loaded, and this screen is also conditional on this error type
*/
const DevServerStoppedErrorScreen = lazy(() =>
import('./ViteDevServerStopped').then((DevServerStopped) => ({
default: DevServerStopped.DevServerStoppedErrorScreen,
})),
)

interface StudioErrorBoundaryProps {
children: ReactNode
heading?: string
Expand Down Expand Up @@ -80,6 +98,10 @@ export const StudioErrorBoundary: ComponentType<StudioErrorBoundaryProps> = ({
return <SchemaErrorsScreen schema={error.schema} />
}

if (error && 'ViteDevServerStoppedError' in error && error.ViteDevServerStoppedError) {
return <DevServerStoppedErrorScreen />
}

if (!error) {
return <ErrorBoundary onCatch={handleCatchError}>{children}</ErrorBoundary>
}
Expand Down
11 changes: 10 additions & 1 deletion packages/sanity/src/core/studio/StudioLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable i18next/no-literal-string, @sanity/i18n/no-attribute-template-literals */
import {Card, Flex} from '@sanity/ui'
import {startCase} from 'lodash'
import {Suspense, useCallback, useEffect, useMemo, useState} from 'react'
import {lazy, Suspense, useCallback, useEffect, useMemo, useState} from 'react'
import {NavbarContext} from 'sanity/_singletons'
import {RouteScope, useRouter, useRouterState} from 'sanity/router'
import {styled} from 'styled-components'
Expand All @@ -18,6 +18,14 @@ import {
import {StudioErrorBoundary} from './StudioErrorBoundary'
import {useWorkspace} from './workspace'

const DetectViteDevServerStopped = lazy(() =>
import('./ViteDevServerStopped').then((DevServerStopped) => ({
default: DevServerStopped.DetectViteDevServerStopped,
})),
)

const detectViteDevServerStopped = import.meta.hot && process.env.NODE_ENV === 'development'
juice49 marked this conversation as resolved.
Show resolved Hide resolved

const SearchFullscreenPortalCard = styled(Card)`
height: 100%;
left: 0;
Expand Down Expand Up @@ -173,6 +181,7 @@ export function StudioLayoutComponent() {
{/* By using the tool name as the key on the error boundary, we force it to re-render
when switching tools, which ensures we don't show the wrong tool having crashed */}
<StudioErrorBoundary key={activeTool?.name} heading={`The ${activeTool?.name} tool crashed`}>
{detectViteDevServerStopped && <DetectViteDevServerStopped />}
<Card flex={1} hidden={searchFullscreenOpen}>
{activeTool && activeToolName && (
<RouteScope
Expand Down
69 changes: 69 additions & 0 deletions packages/sanity/src/core/studio/ViteDevServerStopped.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/* eslint-disable i18next/no-literal-string -- will not support i18n in error boundaries */
import {Card, Container, Heading, Stack, Text} from '@sanity/ui'
import {type ReactNode, useCallback, useEffect, useState} from 'react'
import {type ViteHotContext} from 'vite/types/hot.js'

const ERROR_TITLE = 'Dev server stopped'
const ERROR_DESCRIPTION =
'The development server has stopped. You may need to restart it to continue working.'

class ViteDevServerStoppedError extends Error {
ViteDevServerStoppedError: boolean

constructor() {
super(ERROR_TITLE)
this.name = 'ViteDevServerStoppedError'
this.ViteDevServerStoppedError = true
}
}
const serverHot = import.meta.hot
const isViteServer = (hot: unknown): hot is ViteHotContext => Boolean(hot)

const useDetectViteDevServerStopped = () => {
const [devServerStopped, setDevServerStopped] = useState(false)

const markDevServerStopped = useCallback(() => setDevServerStopped(true), [])

useEffect(() => {
// no early return to optimize tree-shaking
if (isViteServer(serverHot)) {
serverHot.on('vite:ws:disconnect', markDevServerStopped)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@juice49 using import.meta.hot.on. In our version of vite (4.x) there is no .off, however there is in 5.x. Don't think it's a big issue here, since restarting the server will trigger a refresh on the client. But does raise a question around us upgrading perhaps... one for another time

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting omission from the v4 API 🤔. Do you have any concerns around creating multiple subscriptions that are never cleaned up?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not concerned around multiple subscriptions since starting the server again causing a remounting of the entire app, clearing all existing listeners

}
}, [markDevServerStopped])

return {devServerStopped}
}

const ThrowViteServerStopped = () => {
const {devServerStopped} = useDetectViteDevServerStopped()

if (devServerStopped) throw new ViteDevServerStoppedError()

return null
}

export const DetectViteDevServerStopped = (): ReactNode =>
isViteServer(serverHot) ? <ThrowViteServerStopped /> : null

export const DevServerStoppedErrorScreen = (): ReactNode => (
<Card
height="fill"
overflow="auto"
paddingY={[4, 5, 6, 7]}
paddingX={4}
sizing="border"
tone="critical"
>
<Container width={3}>
<Stack space={4}>
<Heading>{ERROR_TITLE}</Heading>

<Card border radius={2} overflow="auto" padding={4} tone="inherit">
<Stack space={4}>
<Text size={2}>{ERROR_DESCRIPTION}</Text>
</Stack>
</Card>
</Stack>
</Container>
</Card>
)
29 changes: 20 additions & 9 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading