diff --git a/packages/core/package.json b/packages/core/package.json index 9a24a6dfe..ef30a0cf3 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -50,8 +50,6 @@ "dependencies": { "deepmerge": "^4.2.2", "ismobilejs": "^1.1.1", - "react-error-boundary": "^3.1.3", "react-use-gesture": "^9.1.3" - }, - "gitHead": "a7dac0f83ad998e205c2aab58182cb4ba4e099a6" + } } \ No newline at end of file diff --git a/packages/core/scripts/build.js b/packages/core/scripts/build.js index a91430b36..896721bc9 100644 --- a/packages/core/scripts/build.js +++ b/packages/core/scripts/build.js @@ -13,7 +13,7 @@ async function main() { esbuild.buildSync({ entryPoints: ['./src/index.ts'], outdir: 'dist/cjs', - minify: false, + minify: true, bundle: true, format: 'cjs', target: 'es6', diff --git a/packages/core/src/components/canvas/canvas.tsx b/packages/core/src/components/canvas/canvas.tsx index 77c3bd169..39f1070b1 100644 --- a/packages/core/src/components/canvas/canvas.tsx +++ b/packages/core/src/components/canvas/canvas.tsx @@ -1,5 +1,4 @@ import * as React from 'react' -import { ErrorBoundary } from 'react-error-boundary' import { usePreventNavigation, useZoomEvents, @@ -9,6 +8,7 @@ import { } from '+hooks' import type { TLBinding, TLPage, TLPageState, TLShape } from '+types' import { ErrorFallback } from '+components/error-fallback' +import { ErrorBoundary } from '+components/error-boundary' import { Brush } from '+components/brush' import { Defs } from '+components/defs' import { Page } from '+components/page' diff --git a/packages/core/src/components/error-boundary/error-boundary.tsx b/packages/core/src/components/error-boundary/error-boundary.tsx new file mode 100644 index 000000000..5079dde8e --- /dev/null +++ b/packages/core/src/components/error-boundary/error-boundary.tsx @@ -0,0 +1,155 @@ +import * as React from 'react' + +// Copied from https://github.com/bvaughn/react-error-boundary/blob/master/src/index.tsx +// (There's an issue with esm builds of this library, so we can't use it directly.) +const changedArray = (a: Array = [], b: Array = []) => + a.length !== b.length || a.some((item, index) => !Object.is(item, b[index])) + +interface FallbackProps { + error: Error + resetErrorBoundary: (...args: Array) => void +} + +interface ErrorBoundaryPropsWithComponent { + onResetKeysChange?: ( + prevResetKeys: Array | undefined, + resetKeys: Array | undefined + ) => void + onReset?: (...args: Array) => void + onError?: (error: Error, info: { componentStack: string }) => void + resetKeys?: Array + fallback?: never + FallbackComponent: React.ComponentType + fallbackRender?: never +} + +declare function FallbackRender( + props: FallbackProps +): React.ReactElement | null + +interface ErrorBoundaryPropsWithRender { + onResetKeysChange?: ( + prevResetKeys: Array | undefined, + resetKeys: Array | undefined + ) => void + onReset?: (...args: Array) => void + onError?: (error: Error, info: { componentStack: string }) => void + resetKeys?: Array + fallback?: never + FallbackComponent?: never + fallbackRender: typeof FallbackRender +} + +interface ErrorBoundaryPropsWithFallback { + onResetKeysChange?: ( + prevResetKeys: Array | undefined, + resetKeys: Array | undefined + ) => void + onReset?: (...args: Array) => void + onError?: (error: Error, info: { componentStack: string }) => void + resetKeys?: Array + fallback: React.ReactElement< + unknown, + string | React.FunctionComponent | typeof React.Component + > | null + FallbackComponent?: never + fallbackRender?: never +} + +type ErrorBoundaryProps = + | ErrorBoundaryPropsWithFallback + | ErrorBoundaryPropsWithComponent + | ErrorBoundaryPropsWithRender + +type ErrorBoundaryState = { error: Error | null } + +const initialState: ErrorBoundaryState = { error: null } + +class ErrorBoundary extends React.Component< + React.PropsWithRef>, + ErrorBoundaryState +> { + static getDerivedStateFromError(error: Error) { + return { error } + } + + state = initialState + updatedWithError = false + resetErrorBoundary = (...args: Array) => { + this.props.onReset?.(...args) + this.reset() + } + + reset() { + this.updatedWithError = false + this.setState(initialState) + } + + componentDidCatch(error: Error, info: React.ErrorInfo) { + this.props.onError?.(error, info) + } + + componentDidMount() { + const { error } = this.state + + if (error !== null) { + this.updatedWithError = true + } + } + + componentDidUpdate(prevProps: ErrorBoundaryProps) { + const { error } = this.state + const { resetKeys } = this.props + + // There's an edge case where if the thing that triggered the error + // happens to *also* be in the resetKeys array, we'd end up resetting + // the error boundary immediately. This would likely trigger a second + // error to be thrown. + // So we make sure that we don't check the resetKeys on the first call + // of cDU after the error is set + if (error !== null && !this.updatedWithError) { + this.updatedWithError = true + return + } + + if (error !== null && changedArray(prevProps.resetKeys, resetKeys)) { + this.props.onResetKeysChange?.(prevProps.resetKeys, resetKeys) + this.reset() + } + } + + render() { + const { error } = this.state + + const { fallbackRender, FallbackComponent, fallback } = this.props + + if (error !== null) { + const props = { + error, + resetErrorBoundary: this.resetErrorBoundary, + } + if (React.isValidElement(fallback)) { + return fallback + } else if (typeof fallbackRender === 'function') { + return fallbackRender(props) + } else if (FallbackComponent) { + return + } else { + throw new Error( + 'react-error-boundary requires either a fallback, fallbackRender, or FallbackComponent prop' + ) + } + } + + return this.props.children + } +} + +export { ErrorBoundary } +export type { + FallbackProps, + ErrorBoundaryPropsWithComponent, + ErrorBoundaryPropsWithRender, + ErrorBoundaryPropsWithFallback, + ErrorBoundaryProps, +} diff --git a/packages/core/src/components/error-boundary/index.ts b/packages/core/src/components/error-boundary/index.ts new file mode 100644 index 000000000..8b96c0703 --- /dev/null +++ b/packages/core/src/components/error-boundary/index.ts @@ -0,0 +1 @@ +export * from './error-boundary' diff --git a/packages/tldraw/package.json b/packages/tldraw/package.json index ecbba2efe..26597a02b 100644 --- a/packages/tldraw/package.json +++ b/packages/tldraw/package.json @@ -63,6 +63,5 @@ "perfect-freehand": "^0.5.2", "react-hotkeys-hook": "^3.4.0", "rko": "^0.5.19" - }, - "gitHead": "4a7439ddf81b615ee49fddbe00802699975f9375" + } } \ No newline at end of file diff --git a/packages/tldraw/scripts/build.js b/packages/tldraw/scripts/build.js index b0bfe3886..cc67ea3b1 100644 --- a/packages/tldraw/scripts/build.js +++ b/packages/tldraw/scripts/build.js @@ -13,7 +13,7 @@ async function main() { esbuild.buildSync({ entryPoints: ['./src/index.ts'], outdir: 'dist/cjs', - minify: false, + minify: true, bundle: true, format: 'cjs', target: 'es6', @@ -26,7 +26,7 @@ async function main() { esbuild.buildSync({ entryPoints: ['./src/index.ts'], outdir: 'dist/esm', - minify: false, + minify: true, bundle: true, format: 'esm', target: 'es6', diff --git a/yarn.lock b/yarn.lock index 34cc53797..a03e64103 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9018,13 +9018,6 @@ react-dom@^17.0.2: object-assign "^4.1.1" scheduler "^0.20.2" -react-error-boundary@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-3.1.3.tgz#276bfa05de8ac17b863587c9e0647522c25e2a0b" - integrity sha512-A+F9HHy9fvt9t8SNDlonq01prnU8AmkjvGKV4kk8seB9kU3xMEO8J/PQlLVmoOIDODl5U2kufSBs4vrWIqhsAA== - dependencies: - "@babel/runtime" "^7.12.5" - react-hotkeys-hook@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/react-hotkeys-hook/-/react-hotkeys-hook-3.4.0.tgz#4d8a81e58eb7ec0d3f6e4fbc4cb0f7830fb6c2aa"