Skip to content

Conversation

@mydea
Copy link
Member

@mydea mydea commented Oct 19, 2023

This introduces a new option for the plugins:

sentryPlugin({
   bundleSizeOptimizations: {
      excludeDebugStatements: true,
      excludePerformanceMonitoring: true,
      excludeReplayCanvas: true,
      excludeReplayIframe: true,
      excludeReplayShadowDom: true,
    },
});

When set, this will replace the relevant build time flags with a boolean, allow bundlers to treeshake code out and reduce the shipped bundle size.

Closes #425

@mydea mydea requested review from Lms24 and lforst October 19, 2023 12:40
@mydea mydea self-assigned this Oct 19, 2023
@mydea mydea force-pushed the fn/bundleSizeOptimizations branch from 5c786dc to c87d6f9 Compare October 19, 2023 12:43
Copy link
Member

@Lms24 Lms24 left a comment

Choose a reason for hiding this comment

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

I like the API for this! Probably easier to use for the majority of folks than defining a replace plugin themselves.

Do you think there's a way that we could filter out files before reading them? I'm thinking about looking at the file path and checking for a @sentry occurance. But not sure if I'm missing something tough why this wouldn't work.

Comment on lines 404 to 411
renderChunk(code: string) {
return replaceBooleanFlagsInCode(code, values);
},

transform(code: string) {
return replaceBooleanFlagsInCode(code, values);
},
};
Copy link
Member

Choose a reason for hiding this comment

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

Why do we need to call the replace function in two hooks? Shouldn't transform be enough?

Copy link
Member Author

Choose a reason for hiding this comment

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

Tbh I just copied this behaviour from the rollup replace Plugin 😅 they to it that way there. But I can just remove the chunk hook!

Comment on lines 243 to 252

const contents = replaceBooleanFlagsInCode(source, values);

if (!contents) {
return null;
}

return { contents: contents.code, loader: "default" };
});
},
Copy link

Choose a reason for hiding this comment

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

We cannot do this as it will override any other registered laoder for files that match this filter. Let's do the following instead:

setup({ initialOptions }) {
  initialOptions.define = { ...initialOptions.define, ...values };
},

Copy link
Member Author

Choose a reason for hiding this comment

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

just to be clear, as I am not an expert at this at all - we do not do this for the other esbuild plugins, which is where I basically copied this from (as well as looking at some esbuild replace plugin). or is this only because we match all files here? E.g. here: https://esbuild.github.io/plugins/#using-plugins they also just return this directly...?

Copy link

Choose a reason for hiding this comment

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

In the other plugins, we are doing very complex logic to make sure all of the user files are still loaded via the plugins they defined. There we are using a sort of proxy logic.

The onLoad hook is simply the wrong approach for this. The onLoad hook, as it suggests is for "reading" files off a particular location. For example, esbuild doesn't know how to files that lie somewhere on the internet (http) so you can define an onLoad hook that takes care of that for esbuild. A file can only be loaded once. So if our plugin loads it, no other plugin will be able to. Doing this in our case will result in catastrophic behavior for users that use any kind of loader plugin - in the way it is implemented right now this will pretty much result in catastrophic behavior for ALL users since we always define the load hook.

Esbuild doesn't have any sort of transform hook and that is by design to keep it fast. It does all the transpilation and stuff by itself. We need to use the define option.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah, that makes sense, thank you for the in depth explanation! I'll adjust this then according to this 🙏

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 updated this to use define (which is much nicer anyhow!)

Comment on lines 403 to 411
return {
renderChunk(code: string) {
return replaceBooleanFlagsInCode(code, values);
},

transform(code: string) {
return replaceBooleanFlagsInCode(code, values);
},
};
Copy link

Choose a reason for hiding this comment

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

We have a slight issue but I don't know yet whether it is gonna be a problem.

We generally instruct people to put our plugin as late as possible, this is necessary for debugID injection. However, in order for minification plugins to be able to properly tree shake the dead code we introduce here, we need to be as early as possible...

I think this is fine with the transform hook, assuming that it is early enough. The render chunk hook is probably too late. I think we can just remove the render chunk hook in general. If we don't catch the statements in transform, we're too late anyhow.

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 will try removing renderChunk and see how it goes. we can always revisit this and add it again later!

}
});

if (ms.hasChanged()) {
Copy link

Choose a reason for hiding this comment

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

this is nice!

Comment on lines +53 to +62
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore webpack version compatibility shenanigans
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
const DefinePlugin =
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore webpack version compatibility shenanigans
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
compiler?.webpack?.DefinePlugin ||
webback4or5?.DefinePlugin ||
webback4or5?.default?.DefinePlugin;
Copy link

Choose a reason for hiding this comment

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

beautiful :D

Copy link
Member Author

Choose a reason for hiding this comment

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

🎨

@lforst
Copy link

lforst commented Oct 19, 2023

Do you think there's a way that we could filter out files before reading them? I'm thinking about looking at the file path and checking for a @sentry occurance. But not sure if I'm missing something tough why this wouldn't work.

Not a bad idea. we could do id.contains(os.sep + "node_modules" + os.sep + "@sentry" + os.sep). This has a few edge cases with resolution but in 99% of the cases it should work.

Counterargument, it would not be the same behavior for all bundlers and why should we make this unnecessarily fragile?

@Lms24
Copy link
Member

Lms24 commented Oct 19, 2023

Counterargument, it would not be the same behavior for all bundlers and why should we make this unnecessarily fragile?

Also a good point. My concern is mostly that the plugin having to read every file might have a performance impact. But I'm totally fine if we just go with this first and optimize later if necessary.

@mydea
Copy link
Member Author

mydea commented Oct 19, 2023

Do you think there's a way that we could filter out files before reading them? I'm thinking about looking at the file path and checking for a @sentry occurance. But not sure if I'm missing something tough why this wouldn't work.

Yeah we can for sure revisit this, since this is opt in I think we can tweak this more later if needed!

@mydea
Copy link
Member Author

mydea commented Oct 20, 2023

OK so I fixed the tests and tried to address the feedback - is this ready to go? :)

I did not add any file filtering for now, I think we can still add this at a later stage if we find out this is too slow! This does not run at all if no bundleSizeOptimizations are configured so it should not affect any existing users.

@lforst
Copy link

lforst commented Oct 20, 2023

This concern is critical and we need to address it before merging this.

Comment on lines 240 to 245
setup({ initialOptions }) {
const replacementStringValues: Record<string, string> = Object.entries(
replacementValues
).reduce((acc, [key, value]) => {
return { ...acc, [key]: JSON.stringify(value) };
}, {});
Copy link

Choose a reason for hiding this comment

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

this is an accidental O(n^2) because of the spread in a loop, I don't think it matters a lot but since this is an easy fix we should just fix it.

@mydea mydea merged commit 2b75688 into main Oct 23, 2023
@mydea mydea deleted the fn/bundleSizeOptimizations branch October 23, 2023 10:20
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.

Add options for treeshaking through build-time flags

4 participants