Skip to content

Commit

Permalink
Merge pull request #2279 from bugsnag/PLAT-13155
Browse files Browse the repository at this point in the history
plugin-react: TypesScript and rollup
  • Loading branch information
djskinner authored Jan 22, 2025
2 parents 8069a5c + 70e450c commit 4202f07
Show file tree
Hide file tree
Showing 13 changed files with 320 additions and 165 deletions.
3 changes: 1 addition & 2 deletions packages/browser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@
"access": "public"
},
"files": [
"dist",
"types"
"dist"
],
"scripts": {
"size": "../../bin/size dist/bugsnag.min.js",
Expand Down
3 changes: 3 additions & 0 deletions packages/plugin-react/babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const babelConfig = require('../../babel.config.js')

module.exports = babelConfig
19 changes: 13 additions & 6 deletions packages/plugin-react/package.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
{
"name": "@bugsnag/plugin-react",
"version": "8.1.1",
"main": "dist/bugsnag-react.js",
"description": "React integration for @bugsnag/js",
"browser": "dist/bugsnag-react.js",
"types": "types/bugsnag-plugin-react.d.ts",
"main": "dist/index-cjs.cjs",
"types": "dist/types/index-es.d.ts",
"browser": "./dist/bugsnag-react.js",
"exports": {
".": {
"types": "./dist/types/index-es.d.ts",
"import": "./dist/index-es.mjs",
"default": "./dist/index-cjs.cjs"
}
},
"homepage": "https://www.bugsnag.com/",
"repository": {
"type": "git",
Expand All @@ -14,12 +21,12 @@
"access": "public"
},
"files": [
"dist",
"types"
"dist"
],
"scripts": {
"clean": "rm -fr dist && mkdir dist",
"build": "npm run clean && ../../bin/bundle src/index.js --standalone=BugsnagPluginReact | ../../bin/extract-source-map dist/bugsnag-react.js"
"build": "npm run clean && npm run build:npm",
"build:npm": "rollup --config rollup.config.npm.mjs"
},
"author": "Bugsnag",
"license": "MIT",
Expand Down
65 changes: 65 additions & 0 deletions packages/plugin-react/rollup.config.npm.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import babel from '@rollup/plugin-babel'
import commonjs from '@rollup/plugin-commonjs'
import nodeResolve from '@rollup/plugin-node-resolve'
import typescript from '@rollup/plugin-typescript'

import createRollupConfig, { sharedOutput } from '../../.rollup/index.mjs'

const plugins = [
nodeResolve({
browser: true
}),
commonjs(),
typescript({
removeComments: true,
// don't output anything if there's a TS error
noEmitOnError: true,
compilerOptions: {
target: 'es2015'
}
}),
babel({ babelHelpers: 'bundled' })
]

const external = ['react']

export default [
createRollupConfig({
input: 'src/index-es.ts',
output: [
{
...sharedOutput,
preserveModules: false,
entryFileNames: '[name].mjs',
format: 'esm'
}
],
external,
plugins
}),
createRollupConfig({
input: 'src/index-cjs.ts',
output: [
{
...sharedOutput,
entryFileNames: '[name].cjs',
format: 'cjs'
},
],
external,
plugins
}),
createRollupConfig({
input: 'src/index-umd.ts',
output: [
{
...sharedOutput,
entryFileNames: 'bugsnag-react.js',
format: 'umd',
name: 'BugsnagReact'
},
],
external,
plugins
})
]
5 changes: 5 additions & 0 deletions packages/plugin-react/src/index-cjs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import BugsnagPluginReact, { formatComponentStack } from './plugin'

import assign from '@bugsnag/core/lib/es-utils/assign'

export default assign(BugsnagPluginReact, { formatComponentStack })
1 change: 1 addition & 0 deletions packages/plugin-react/src/index-es.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default, formatComponentStack } from './plugin'
5 changes: 5 additions & 0 deletions packages/plugin-react/src/index-umd.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import BugsnagPluginReact, { formatComponentStack } from './plugin'

import assign from '@bugsnag/core/lib/es-utils/assign'

export default assign(BugsnagPluginReact, { formatComponentStack })
93 changes: 0 additions & 93 deletions packages/plugin-react/src/index.js

This file was deleted.

120 changes: 120 additions & 0 deletions packages/plugin-react/src/plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import React, { ErrorInfo } from 'react'

import { Plugin, OnErrorCallback, Client } from '@bugsnag/core'

interface BugsnagErrorBoundaryProps {
children?: React.ReactNode | undefined
onError?: OnErrorCallback
FallbackComponent?: React.ComponentType<{
error: Error
info: React.ErrorInfo
clearError: () => void
}>
}

export type BugsnagErrorBoundary = React.ComponentType<BugsnagErrorBoundaryProps>

export interface BugsnagPluginReactResult {
createErrorBoundary(react?: typeof React): BugsnagErrorBoundary
}

// add a new call signature for the getPlugin() method that types the react plugin result
declare module '@bugsnag/core' {
interface Client {
getPlugin(id: 'react'): BugsnagPluginReactResult | undefined
}
}

export default class BugsnagPluginReact implements Plugin {
public readonly name: string;
private readonly lazy: boolean;
private readonly React?: typeof React

constructor (react?: typeof React) {
// Fetch React from the window object, if it exists
const globalReact = typeof window !== 'undefined' && window.React

this.name = 'react'
this.lazy = !react && !globalReact

if (!this.lazy) {
this.React = react || globalReact as typeof React
if (!this.React) throw new Error('@bugsnag/plugin-react reference to `React` was undefined')
}
}

load (client: Client) {
if (!this.lazy && this.React) {
const ErrorBoundary = createClass(this.React, client)
return { createErrorBoundary: () => ErrorBoundary }
}

const BugsnagPluginReactLazyInitializer = function () {
throw new Error(`@bugsnag/plugin-react was used incorrectly. Valid usage is as follows:
Pass React to the plugin constructor
\`Bugsnag.start({ plugins: [new BugsnagPluginReact(React)] })\`
and then call \`const ErrorBoundary = Bugsnag.getPlugin('react').createErrorBoundary()\`
Or if React is not available until after Bugsnag has started,
construct the plugin with no arguments
\`Bugsnag.start({ plugins: [new BugsnagPluginReact()] })\`,
then pass in React when available to construct your error boundary
\`const ErrorBoundary = Bugsnag.getPlugin('react').createErrorBoundary(React)\``)
}
BugsnagPluginReactLazyInitializer.createErrorBoundary = (react: typeof React) => {
if (!react) throw new Error('@bugsnag/plugin-react reference to `React` was undefined')
return createClass(react, client)
}
return BugsnagPluginReactLazyInitializer
}
}

export const formatComponentStack = (str: string) => {
const lines = str.split(/\n/g)
let ret = ''
for (let line = 0, len = lines.length; line < len; line++) {
if (lines[line].length) ret += `${ret.length ? '\n' : ''}${lines[line].trim()}`
}
return ret
}

const createClass = (react: typeof React, client: Client) => class BugsnagErrorBoundary extends React.Component<BugsnagErrorBoundaryProps, { errorState: null | { error: Error, info: ErrorInfo }}> {
constructor (props: BugsnagErrorBoundaryProps) {
super(props)
this.state = {
errorState: null
}
this.handleClearError = this.handleClearError.bind(this)
}

handleClearError () {
this.setState({ errorState: null })
}

componentDidCatch (error: Error, info: ErrorInfo) {
const { onError } = this.props
const handledState = { severity: 'error', unhandled: true, severityReason: { type: 'unhandledException' } }
// @ts-ignore internal API
const event = client.Event.create(
error,
true,
handledState,
1
)
if (info && info.componentStack) info.componentStack = formatComponentStack(info.componentStack)
event.addMetadata('react', info)
client._notify(event, onError)
this.setState({ errorState: { error, info } })
}

render () {
const { errorState } = this.state
if (errorState) {
const { FallbackComponent } = this.props
if (FallbackComponent) return react.createElement(FallbackComponent, { ...errorState, clearError: this.handleClearError })
return null
}
return this.props.children
}
}
3 changes: 0 additions & 3 deletions packages/plugin-react/src/test/.babelrc

This file was deleted.

15 changes: 0 additions & 15 deletions packages/plugin-react/src/test/__snapshots__/index.test.tsx.snap

This file was deleted.

Loading

0 comments on commit 4202f07

Please sign in to comment.