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

convert Client, Session, Breadcrumb, Event, Config to TypeScript #2323

Merged
merged 5 commits into from
Feb 17, 2025

Conversation

djskinner
Copy link
Contributor

@djskinner djskinner commented Feb 13, 2025

convert Client, Session, Breadcrumb, Event, Config to TypeScript

I've attempted to minimise changes to the actual source code hence the proliferation of @ts-ignore directives. The reality is that some approaches that have been used when written in JavaScript (example the session delegate) aren't best suited to static typing.

Copy link

github-actions bot commented Feb 14, 2025

@bugsnag/browser bundle size diff

Minified Minfied + Gzipped
Before 51.47 kB 15.76 kB
After 55.62 kB 16.68 kB
± ⚠️ +4,156 bytes ⚠️ +921 bytes

code coverage diff

<temporarily disabled>

Generated by 🚫 dangerJS against 4f7a2d8

@@ -58,6 +58,15 @@ const c = tseslint.config(
{
files: ['**/*.json', '**/*.[tj]s?(x)', '**/*.cjs', '**/*.mjs'],
},
// Node environment
{
files: ['jest/**/*.[js|mjs]', '**/*/babel.config.js', '**/*/rollup.config.mjs', '.rollup/index.mjs'],
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure the best way to target these. I think maybe we could assume all [js|mjs] are node environment then override the globals later to remove node for certain browser-only packages etc.

@@ -116,7 +116,9 @@ const notifier: BrowserClient = {

type Method = keyof typeof Client.prototype

map(['resetEventCount'].concat(keys(Client.prototype)) as Method[], (m) => {
const clientMethods = Object.getOwnPropertyNames(Client.prototype).concat(['resetEventCount']) as Method[]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well something has changed meaning that the class properties are now non-enumerable so keys(Client.prototype) (or Object.keys) no longer works.

@@ -1,3 +1,6 @@
const babelConfig = require('../../babel.config.js')
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right now for most packages we have TypeScript doing the compilation. However, this seemed to create an issue because parts of core use the spread operator, and TypeScript was complaining about a lack of tslib. After installing tslib then the whole of it appears in the bundle but also being imported at an absolute path unique to my machine.

What I've done here and I think we could do for other packages is to have babel do all the transpiling, leaving TypeScript to generate the type declarations only.

This could be handy as babel is more more granular in what transforms are applied and I think it will give us more control over the bundle size. With TypeScript its "target es5" and no further configuration. This approach is also mentioned in the docs at https://www.npmjs.com/package/@rollup/plugin-typescript.

noForceEmit can be very useful if you use with @rollup/plugin-babel and @babel/preset-typescript. Having @rollup/plugin-typescript only do typechecking / declarations with "emitDeclarationOnly": true while deferring to @rollup/plugin-babel for transpilation can dramatically reduce rollup build times for large projects.

By more granular I mean we can look at using @babel/preset-env and browserslist to make it easier to maintain support levels whilst also having the option of adding/removing additional plugins for whatever other transforms we want e.g. for controlling bundle size

As a side note I have noted the increase in bundle size in integration/typescript but I'm confident we can get it back closer to where it was

]

const external = [/node_modules/]

export default [
createRollupConfig({
{
Copy link
Contributor Author

Choose a reason for hiding this comment

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

createRollupConfig is becoming a problem now. Basically we're overriding everything anyway, except you can't override the typescript plugin it adds

Copy link
Member

Choose a reason for hiding this comment

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

I think it was basically used to create config for anything except the core package in the performance repo. if it's not working don't feel like we have to make it work.

Comment on lines +63 to +64
// This ought to be Required<T> but the current version of TypeScript doesn't seem to like it
public readonly _config: Required<Config>
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I did mange to work this out in the end and sorted it in a subsequent PR:

interface Config {
    someVal?: string
}

class Client<T extends Config> {
    private readonly _config: T & Required<Config>;

    constructor(config: T) {
        this._config = { ...config, someVal: 'hello' };
    }

    public helloWorld() {
        this._config.someVal.length
    }
}

Base automatically changed from plat-12947-6 to integration/typescript February 14, 2025 13:58
@@ -270,6 +270,7 @@ describe('Client', () => {
})
client._setDelivery(client => ({
sendEvent: (payload) => {
// @ts-expect-error Property 'errorMessage' does not exist on type 'Error'
Copy link
Contributor Author

Choose a reason for hiding this comment

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

These are fixed in the next PR by introducing a BugsnagError interface

@@ -21,6 +21,7 @@ describe('Event', () => {
{ foo: 10 },
{ toJSON: () => { throw new Error('do not serialise me, srsly') } }
])
// @ts-expect-error Property 'stacktrace' does not exist on type 'Error'
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in the next PR by introducing a BugsnagError interface

this._user = { id, email, name }
}

toJSON () {
return {
payloadVersion: '4',
// @ts-expect-error - TS doesn't know that the error object has an errorMessage property
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in the next PR by introducing a BugsnagError interface

@@ -6,10 +6,8 @@ module.exports = api => {
const overrides = []

if (api && api.env('test')) {
presets.push(['@babel/preset-env', {targets: {node: 'current'}}])
Copy link
Contributor Author

Choose a reason for hiding this comment

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

in jest we only need to apply the transforms that are needed for whatever node environment we're running

Comment on lines +90 to +93
private readonly Client: typeof Client
private readonly Event: typeof Event
private readonly Breadcrumb: typeof Breadcrumb
private readonly Session: typeof Session
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've generally started out with things being private readonly and loosened as required. In the next PR where the generated type defs are used instead of the manually created ones even more of this properties become public

@djskinner djskinner marked this pull request as ready for review February 17, 2025 11:42
@djskinner djskinner requested a review from gingerbenw February 17, 2025 11:42
@djskinner djskinner merged commit 0b2a75e into integration/typescript Feb 17, 2025
54 of 55 checks passed
@djskinner djskinner deleted the plat-12947-7 branch February 17, 2025 13:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants