Skip to content

feat(build): Add polyfill-extraction rollup plugin #5023

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

Merged
merged 3 commits into from
May 10, 2022

Conversation

lobsterkatie
Copy link
Member

@lobsterkatie lobsterkatie commented May 3, 2022

This is a follow-up to #5051, which added our own versions of the polyfills that Rollup and Sucrase inject during build, in order to be able to import them rather than have their code repeated in every file in which they're needed, essentially replicating the behavior of tslib's importHelpers option. This completes that change, by adding a rollup plugin to the build process which extracts the injected code and replaces it with an equivalent import or require statement. Because the import comes from @sentry/utils, the few packages which didn't already depend on it have had it added as a dependency.

Note: Originally, the changes from #5051 were included in this PR, and that is reflected in a number of the comments below. For ease of reviewing, they were split into their own PR.

Ref: https://getsentry.atlassian.net/browse/WEB-858

@lobsterkatie lobsterkatie force-pushed the kmclb-add-polyfill-extraction-rollup-plugin branch 3 times, most recently from f02192e to ce0f043 Compare May 3, 2022 05:10
@github-actions
Copy link
Contributor

github-actions bot commented May 3, 2022

size-limit report 📦

Path Size
@sentry/browser - ES5 CDN Bundle (gzipped + minified) 18.76 KB (-6.87% 🔽)
@sentry/browser - ES5 CDN Bundle (minified) 58.58 KB (-9.35% 🔽)
@sentry/browser - ES6 CDN Bundle (gzipped + minified) 17.55 KB (-6.97% 🔽)
@sentry/browser - ES6 CDN Bundle (minified) 52.76 KB (-9% 🔽)
@sentry/browser - Webpack (gzipped + minified) 19.21 KB (-17.35% 🔽)
@sentry/browser - Webpack (minified) 62.12 KB (-23.99% 🔽)
@sentry/react - Webpack (gzipped + minified) 19.23 KB (-17.39% 🔽)
@sentry/nextjs Client - Webpack (gzipped + minified) 42.75 KB (-11.04% 🔽)
@sentry/browser + @sentry/tracing - ES5 CDN Bundle (gzipped + minified) 24.42 KB (-6.34% 🔽)
@sentry/browser + @sentry/tracing - ES6 CDN Bundle (gzipped + minified) 23 KB (-6.08% 🔽)

@lobsterkatie lobsterkatie force-pushed the kmclb-add-polyfill-extraction-rollup-plugin branch 2 times, most recently from d3a844a to ad4ce22 Compare May 3, 2022 06:29
@AbhiPrasad
Copy link
Member

Does this mean we can remove tslib as a dependency?

@AbhiPrasad AbhiPrasad added this to the 7.0.0 milestone May 3, 2022
@lobsterkatie lobsterkatie force-pushed the kmclb-add-polyfill-extraction-rollup-plugin branch from ad4ce22 to f10ba18 Compare May 3, 2022 13:07
@lobsterkatie
Copy link
Member Author

Does this mean we can remove tslib as a dependency?

Once we switch over, yes, that's the idea. We're basically making our own tslib.

const importSource = literal(
isUtilsPackage
? path.relative(path.dirname(currentSourceFile), `../jsPolyfills/${moduleFormat}`)
: `@sentry/utils/jsPolyfills/${moduleFormat}`,
Copy link
Member

Choose a reason for hiding this comment

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

should we make another package for the polyfills?

Copy link
Member Author

Choose a reason for hiding this comment

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

In the long term, they might well live in a @sentry/dev-utils/@sentry/internal-utils/@sentry/whatever package, but to get things running, for now the rollup directory seemed like a good place

In the long run I agree that'd be a better place, but I'd say it's okay to leave this as an iterative improvement. What do you think?

Copy link
Member

@Lms24 Lms24 May 4, 2022

Choose a reason for hiding this comment

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

I'm +1 on an extra package but it's probably fine if we do it in a follow-up task.

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 don't think they warrant a package all on their own, but I did experiment with moving them to a dev-utils package. Once I got them there, though, I didn't love that it was then going to become a runtime dependency of @sentry/utils, when it really should only be a dev dependency. And as part of that experimentation, I did end up translating them to TS, which was the final remaining blocker to them living directly in @sentry/utils, so that is where they ended up.

(The other two original blockers for me were:

  • Feeling weird about putting code that wasn't ours in with code that was. This was solved for me by 1) the fact that we now go overboard in terms of attribution, license-copying, etc, and 2) the fact that in fact they aren't exact copies of the originals anymore, if for no other reason than the translation to TS.
  • Not wanting them to be part of the public API of @sentry/utils. Originally that meant that I was copying them over to a folder outside of build, but I realized that their built versions actually can live inside build and still be "hidden" (in that they won't show up in intellisense completions, for example) as long as they're not exported from index.ts. That then let me put the folder containing the pre-build versions straight into src/, which made everything about building and testing them a lot easier.)

@lobsterkatie lobsterkatie force-pushed the kmclb-add-polyfill-extraction-rollup-plugin branch from f10ba18 to 1f2dee5 Compare May 3, 2022 13:47
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.

So I took a look at the PR and overall this looks good to me with the asterisk that I'm not entirely comfortable with having our "own" polyfills because to me this seems kind of strange. But then again, I clearly don't have as much context here as you so please take it with a grain of salt.

I do have a few questions though:

  • Just to double check - there isn't a way around having our own polyfills, right? I was thinking of something like importing them from sucrase/rollup/tslib instead of adding them to our repo where we have to maintain them. In my mind it would be best to just import them and add the importHelpers functionality around them. But again, I probably lack a lot of necessary context here.
  • The polyfills we're adding to our repo in this PR - are they exact copies of the Sucrase/Rollup polyfilles?
    • If yes, do we need to acknowledge this in the code files/give credits, etc?
    • if no, I really think we should have tests for them (would be best either way, I guess)

Anyway, great work and thanks for figuring all of this out!

@lobsterkatie
Copy link
Member Author

lobsterkatie commented May 4, 2022

  • Just to double check - there isn't a way around having our own polyfills, right? I was thinking of something like importing them from sucrase/rollup/tslib instead of adding them to our repo where we have to maintain them.

Believe me, I wish I saw a way around it, because I agree, I'd rather leave maintenance/testing/etc to them instead of having to do it ourselves. But it's unfortunately not as simple as importing the helpers, because they don't exist in a straightforwardly importable form. Sucrase uses a HelperManager class, which takes string versions of the functions, changes their names, and then injects them into each file, and Rollup generates the code itself on the fly before injecting it. So the alternative to vendoring the polyfills would be to try to tap into their polyfill-generation machinery, which felt a lot more brittle, as it's much more likely they'd change that than the actual code of the helpers (which are simple, dependent on nothing, and solving a known and fixed problem, all of which means they're likely to be highly stable over time). It also seemed like it'd also be much easier for us to mess that up, compared to just grabbing the helpers in their final form and throwing them into a folder.

  • The polyfills we're adding to our repo in this PR - are they exact copies of the Sucrase/Rollup polyfilles?

In the case of everything other than the iterop helpers, yes, they're the same as what's emitted by sucrase (the only ones emitted by rollup are the interop ones). As far as the interop ones go, here are the originals (we use either the esmodule option or the auto option, depending on the package's current esModuleInterop value). The versions included in this PR are definitely simplified, but (I claim) not in a way which changes the outcome. (For example, I turned var n = Object.create(null) into var n = {}, and I mimicked the behavior of setting output.externalLiveBindings to false and output.freeze to false.)

  • If yes, do we need to acknowledge this in the code files/give credits, etc?

From the first line of the README distributed with the polyfills: "This is a collection of syntax polyfills either copied directly from or heavily inspired by those used by Rollup and Sucrase." What do you think, is more necessary?

  • if no, I really think we should have tests for them (would be best either way, I guess)

For those interop ones, I'm happy to work on tests now if it would up our overall confidence level in making this change. I'll update here once I've added some.

UPDATE: The tests have indeed been added, but they increased the size of this PR sufficiently that I thought it would be easier to review if I split it in two. They can be found in #5051.

@lobsterkatie lobsterkatie force-pushed the kmclb-add-polyfill-extraction-rollup-plugin branch 7 times, most recently from a3283e3 to 2d66612 Compare May 9, 2022 05:30
@lobsterkatie lobsterkatie force-pushed the kmclb-add-polyfill-extraction-rollup-plugin branch from 2d66612 to b5bf453 Compare May 9, 2022 06:06
@lobsterkatie lobsterkatie requested review from Lms24 and AbhiPrasad May 9, 2022 13:09
lobsterkatie added a commit that referenced this pull request May 9, 2022
Both Rollup and Sucrase add polyfills (similar to those in `tslib`) during the build process, to down-compile newer language features and help ESM and CJS modules play nicely together. Unlike with `tslib`, however, there's no option equivalent to tsc's `importHelpers` option[1], which allows those polyfills to be imports from one central place rather than copies of the full code for each necessary polyfill in every file that uses them. Having all that repeated code is a surefire way to wreck our bundle-size progress, so as part of the switch to using those tools, we will replicate the behavior of `importHelpers` in the context of our new sucrase/rollup builds.

This is the first of two PRs accomplishing that change. In this one, the polyfills we'll use have been added to the repo. In the next one, a rollup plugin will be added to replace the duplicated injected code with import statements, which will then import the polyfills this PR adds. Originally this was one PR, but it has been split into two for easier reviewing.

Notes:

- The polyfills themselves have been added to `@sentry/utils`, in a separate folder under `src/`. Various discussions have been had about where they should live[2], and they have moved around as this PR evolved. Without going into too much boring detail about about the exact pros and cons of each location, suffice it to say that my first instinct was to put them in `utils`, and as I worked on other options, each one of the reasons why I didn't actually do that to begin with (they weren't in TS, they weren't originally ours, I didn't want to pollute the `@sentry/utils` namespace) got solved, and in the end, putting them in `utils` was the simplest and most logical option.
- The added polyfills are copies of the code injected by Sucrase or Rollup, translated into TS and in some cases modified in order to make them less verbose. Since some treeshaking algorithms can only work file by file, each one is in its own file. Polyfills which were modified have had tests added, both to ensure that they do the same thing as the originals, and to ensure that what they do is actually correct.
- As we're not the original authors, attribution has been given within each file to the original source. Further, since the MIT license under which both Rollup and Sucrase are distributed requires duplication of the license blurb, it has been added to the README in the polyfills' folder.
- Because we don't want these to become part of our public API, their code is not exported by `index.ts`, meaning they are not importable directly from `@sentry/utils` and won't come up under intellisense code completions and the like. When our code imports them, it will import from `@sentry/utils/cjs/buildPolyfills` or `@sentry/utils/esm/buildPolyfills` as appropriate.
- Though not every polyfill Rollup and Sucrase can inject ends up being used in our built code, they are all included just to cover our bases. That said, in the interest of time, only the ones we use or are likely to use have had tests written for them. 
- One of the polyfills we do use is the optional chain polyfill. Typing it is tricky, because it takes a variable number of arguments, whose types are effectively `[ A, B, C, B, C, B, C,... ]` (one type for the first argument, and then alternating types for the rest). Because it's not going to be used by the public, the typing in the polyfill itself uses the generic `unknown`. For tests it was easier, because only the test cases needed to be typed, and could have a slightly modified structure: `[ A, [ B, C ][] ]`, which then gets flattened again before being passed to the optional chain polyfill. In order to be able to use `Array.prototype.flat` in tests run on older versions of Node, it itself has been polyfilled, and our testing `tsconfig` target ramped back down when running tests under those old versions.

[1] https://www.typescriptlang.org/tsconfig#importHelpers
[2] #5023 (comment)
@lobsterkatie lobsterkatie force-pushed the kmclb-add-polyfill-extraction-rollup-plugin branch from b5bf453 to abce946 Compare May 9, 2022 17:06
@lobsterkatie lobsterkatie merged commit cb9260f into 7.x May 10, 2022
@lobsterkatie lobsterkatie deleted the kmclb-add-polyfill-extraction-rollup-plugin branch May 10, 2022 13:26
lobsterkatie added a commit that referenced this pull request May 10, 2022
This switches our `build` and `build:dev` yarn scripts to use the new rollup/sucrase build process.

This is the culmination of a number of previous changes, whose highlights include:
- #4724, which made building types files a separate build process
- #4895, which updated the SDK to use TS 3.8.3
- #4926, which removed use of the `Severity` enum
- #5005, which switch our default tsconfig to target es6
- #4992, which added the Sucrase plugin, some helper functions, and the `yarn build:rollup` script
- #4993, which added rollup plugins to use `var` rather than `const` and clean up the built code in various ways
- #5022, which applied the same `const`-to-`var` translation to tests
- #5023, which added the ability to change injected polyfills into imports

The result is that, as of this PR, we will no longer use `tsc` to transpile or down-complile our code when building npm packages. Instead, we will be using Rollup to handle making our code CJS-friendlly and Sucrase to handle the transpilation from TS to JS. The main advantages of this change are:
- It forced us to do a lot of updating, centralizing, and cleanup of our tooling, not just for build but also for testing and linting.
- It brings our CDN build process and our npm build process much more in line with each other, for easier maintainability.
- It gives us more control over the eventual output, because we have access to a whole internet full of Rollup plugins (not to mention the ability to make our own), rather than being constrained to tsconfig options. (Plugins also allow us to interact with the code directly.)
- It speeds up our builds fairly significantly. I ran a number of trials in GHA of running `yarn build:dev` at the top level of the repo. Before this change, the average time was ~150 seconds. After this change, it's about half that, roughly 75 seconds.

Because of the switch in tooling, the code we publish is going to be slightly different. In order to make sure that those differences weren't going to be breaking, I built each package under the old system and under the new system, ran a `git diff`, and checked every file, both CJS and ESM, in every package affected by this change. The differences (none of which affect behavior or eventual bundle size by more than a few bytes in each direction), fell into a few categories:

- Purely cosmetic changes, things like which comments are retained, the order of imports, where in the file exports live, etc.
- Changes to class constructors, things like not explicitly assigning `undefined` to undefined attributes, using regular assignment rather than `Object.defineProperty` for attributes which are assigned values, and splitting some of those assignments off into helper functions. 
- Changes related to the upgrade to ES6 and dropping of support for Node 6, things like not polyfilling object spread or async/await

While this represents the most significant part of the overall change, a few outstanding tasks remain:

- Making this same switch in `build:watch`
- Parallelizing the builds, both locally and in CI
- Perhaps applying the new process to our CDN bundle builds
- Generalized cleanup

These will all be included in separate PRs, some in the immediate future and some in the hopefully-not-too-distant short-to-medium term.
AbhiPrasad pushed a commit that referenced this pull request May 30, 2022
Both Rollup and Sucrase add polyfills (similar to those in `tslib`) during the build process, to down-compile newer language features and help ESM and CJS modules play nicely together. Unlike with `tslib`, however, there's no option equivalent to tsc's `importHelpers` option[1], which allows those polyfills to be imports from one central place rather than copies of the full code for each necessary polyfill in every file that uses them. Having all that repeated code is a surefire way to wreck our bundle-size progress, so as part of the switch to using those tools, we will replicate the behavior of `importHelpers` in the context of our new sucrase/rollup builds.

This is the first of two PRs accomplishing that change. In this one, the polyfills we'll use have been added to the repo. In the next one, a rollup plugin will be added to replace the duplicated injected code with import statements, which will then import the polyfills this PR adds. Originally this was one PR, but it has been split into two for easier reviewing.

Notes:

- The polyfills themselves have been added to `@sentry/utils`, in a separate folder under `src/`. Various discussions have been had about where they should live[2], and they have moved around as this PR evolved. Without going into too much boring detail about about the exact pros and cons of each location, suffice it to say that my first instinct was to put them in `utils`, and as I worked on other options, each one of the reasons why I didn't actually do that to begin with (they weren't in TS, they weren't originally ours, I didn't want to pollute the `@sentry/utils` namespace) got solved, and in the end, putting them in `utils` was the simplest and most logical option.
- The added polyfills are copies of the code injected by Sucrase or Rollup, translated into TS and in some cases modified in order to make them less verbose. Since some treeshaking algorithms can only work file by file, each one is in its own file. Polyfills which were modified have had tests added, both to ensure that they do the same thing as the originals, and to ensure that what they do is actually correct.
- As we're not the original authors, attribution has been given within each file to the original source. Further, since the MIT license under which both Rollup and Sucrase are distributed requires duplication of the license blurb, it has been added to the README in the polyfills' folder.
- Because we don't want these to become part of our public API, their code is not exported by `index.ts`, meaning they are not importable directly from `@sentry/utils` and won't come up under intellisense code completions and the like. When our code imports them, it will import from `@sentry/utils/cjs/buildPolyfills` or `@sentry/utils/esm/buildPolyfills` as appropriate.
- Though not every polyfill Rollup and Sucrase can inject ends up being used in our built code, they are all included just to cover our bases. That said, in the interest of time, only the ones we use or are likely to use have had tests written for them. 
- One of the polyfills we do use is the optional chain polyfill. Typing it is tricky, because it takes a variable number of arguments, whose types are effectively `[ A, B, C, B, C, B, C,... ]` (one type for the first argument, and then alternating types for the rest). Because it's not going to be used by the public, the typing in the polyfill itself uses the generic `unknown`. For tests it was easier, because only the test cases needed to be typed, and could have a slightly modified structure: `[ A, [ B, C ][] ]`, which then gets flattened again before being passed to the optional chain polyfill. In order to be able to use `Array.prototype.flat` in tests run on older versions of Node, it itself has been polyfilled, and our testing `tsconfig` target ramped back down when running tests under those old versions.

[1] https://www.typescriptlang.org/tsconfig#importHelpers
[2] #5023 (comment)
AbhiPrasad pushed a commit that referenced this pull request May 30, 2022
This is a follow-up to #5051, which added our own versions of the polyfills that Rollup and Sucrase inject during build, in order to be able to import them rather than have their code repeated in every file in which they're needed, essentially replicating the behavior of `tslib`'s `importHelpers` option. This completes that change, by adding a rollup plugin to the build process which extracts the injected code and replaces it with an equivalent `import` or `require` statement. Because the import comes from `@sentry/utils`, the few packages which didn't already depend on it have had it added as a dependency.
AbhiPrasad pushed a commit that referenced this pull request May 30, 2022
This switches our `build` and `build:dev` yarn scripts to use the new rollup/sucrase build process.

This is the culmination of a number of previous changes, whose highlights include:
- #4724, which made building types files a separate build process
- #4895, which updated the SDK to use TS 3.8.3
- #4926, which removed use of the `Severity` enum
- #5005, which switch our default tsconfig to target es6
- #4992, which added the Sucrase plugin, some helper functions, and the `yarn build:rollup` script
- #4993, which added rollup plugins to use `var` rather than `const` and clean up the built code in various ways
- #5022, which applied the same `const`-to-`var` translation to tests
- #5023, which added the ability to change injected polyfills into imports

The result is that, as of this PR, we will no longer use `tsc` to transpile or down-complile our code when building npm packages. Instead, we will be using Rollup to handle making our code CJS-friendlly and Sucrase to handle the transpilation from TS to JS. The main advantages of this change are:
- It forced us to do a lot of updating, centralizing, and cleanup of our tooling, not just for build but also for testing and linting.
- It brings our CDN build process and our npm build process much more in line with each other, for easier maintainability.
- It gives us more control over the eventual output, because we have access to a whole internet full of Rollup plugins (not to mention the ability to make our own), rather than being constrained to tsconfig options. (Plugins also allow us to interact with the code directly.)
- It speeds up our builds fairly significantly. I ran a number of trials in GHA of running `yarn build:dev` at the top level of the repo. Before this change, the average time was ~150 seconds. After this change, it's about half that, roughly 75 seconds.

Because of the switch in tooling, the code we publish is going to be slightly different. In order to make sure that those differences weren't going to be breaking, I built each package under the old system and under the new system, ran a `git diff`, and checked every file, both CJS and ESM, in every package affected by this change. The differences (none of which affect behavior or eventual bundle size by more than a few bytes in each direction), fell into a few categories:

- Purely cosmetic changes, things like which comments are retained, the order of imports, where in the file exports live, etc.
- Changes to class constructors, things like not explicitly assigning `undefined` to undefined attributes, using regular assignment rather than `Object.defineProperty` for attributes which are assigned values, and splitting some of those assignments off into helper functions. 
- Changes related to the upgrade to ES6 and dropping of support for Node 6, things like not polyfilling object spread or async/await

While this represents the most significant part of the overall change, a few outstanding tasks remain:

- Making this same switch in `build:watch`
- Parallelizing the builds, both locally and in CI
- Perhaps applying the new process to our CDN bundle builds
- Generalized cleanup

These will all be included in separate PRs, some in the immediate future and some in the hopefully-not-too-distant short-to-medium term.
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.

3 participants