Skip to content

Commit e0f199f

Browse files
authored
chore(build): Replace sucrase with esbuild (#20865)
This PR updates our rollup config to use esbuild instead of sucrase/typescript plugins. 1. We relied on a custom, kind of weird fork of sucrase here to get the proper es support we cared about with minimal bundle size. this is not really ideal... 2. We also still had some code paths (for the replay worker) relying on typescript plugin, which we have since dropped in other places already. this PR unifies this to use esbuild everywhere. 3. This also allows us to get rid of the cleanup plugin as this is handled by esbuild already anyhow. 4. Using esbuild is a more future proof solution here, we can easily update es support and it will handle it for us. 5. Removes any rollup dependencies we actually no longer use (some where orphaned already) Doing this, required a few changes to our rollup plugin setup, which all should be generally good changes IMHO: * With esbuild, the order of some of our plugins becomes more important, as esbuild strips certain comments etc. more aggressively. This PR takes care of this by being more explicit in the order of plugins and enforcing this everywhere. Overall bundle size seems stable, slight reductions in a few places - execpt for cloudflare, which actually has a 3.5kb reduction for whatever reason.
1 parent 5f68f66 commit e0f199f

21 files changed

Lines changed: 216 additions & 380 deletions

File tree

dev-packages/rollup-utils/bundleHelpers.mjs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,13 @@ import deepMerge from 'deepmerge';
88

99
import {
1010
makeBrowserBuildPlugin,
11-
makeCleanupPlugin,
1211
makeCommonJSPlugin,
12+
makeEsbuildPlugin,
1313
makeIsDebugBuildPlugin,
1414
makeLicensePlugin,
1515
makeNodeResolvePlugin,
1616
makeRrwebBuildPlugin,
1717
makeSetSDKSourcePlugin,
18-
makeSucrasePlugin,
1918
makeTerserPlugin,
2019
} from './plugins/index.mjs';
2120
import { mergePlugins } from './utils.mjs';
@@ -24,11 +23,10 @@ import { makeProductionReplacePlugin } from './plugins/npmPlugins.mjs';
2423
const BUNDLE_VARIANTS = ['.js', '.min.js', '.debug.min.js'];
2524

2625
export function makeBaseBundleConfig(options) {
27-
const { bundleType, entrypoints, licenseTitle, outputFileBase, packageSpecificConfig, sucrase } = options;
26+
const { bundleType, entrypoints, licenseTitle, outputFileBase, packageSpecificConfig, esbuild } = options;
2827

2928
const nodeResolvePlugin = makeNodeResolvePlugin();
30-
const sucrasePlugin = makeSucrasePlugin({}, sucrase);
31-
const cleanupPlugin = makeCleanupPlugin();
29+
const transpilePlugin = makeEsbuildPlugin(esbuild);
3230
const markAsBrowserBuildPlugin = makeBrowserBuildPlugin(true);
3331
const licensePlugin = makeLicensePlugin(licenseTitle);
3432
const rrwebBuildPlugin = makeRrwebBuildPlugin({
@@ -118,7 +116,7 @@ export function makeBaseBundleConfig(options) {
118116
strict: false,
119117
esModule: false,
120118
},
121-
plugins: [productionReplacePlugin, sucrasePlugin, nodeResolvePlugin, cleanupPlugin],
119+
plugins: [productionReplacePlugin, transpilePlugin, nodeResolvePlugin],
122120
treeshake: 'smallest',
123121
};
124122

dev-packages/rollup-utils/npmHelpers.mjs

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,11 @@ import deepMerge from 'deepmerge';
1313

1414
import { defineConfig } from 'rollup';
1515
import {
16-
makeCleanupPlugin,
1716
makeDebugBuildStatementReplacePlugin,
17+
makeEsbuildPlugin,
1818
makeNodeResolvePlugin,
1919
makeProductionReplacePlugin,
2020
makeRrwebBuildPlugin,
21-
makeSucrasePlugin,
2221
} from './plugins/index.mjs';
2322
import { makePackageNodeEsm } from './plugins/make-esm-plugin.mjs';
2423
import { mergeExternals, mergePlugins } from './utils.mjs';
@@ -34,14 +33,13 @@ export function makeBaseNPMConfig(options = {}) {
3433
entrypoints = ['src/index.ts'],
3534
hasBundles = false,
3635
packageSpecificConfig = {},
37-
sucrase = {},
36+
esbuild = {},
3837
bundledBuiltins = [],
3938
} = options;
4039

4140
const nodeResolvePlugin = makeNodeResolvePlugin();
42-
const sucrasePlugin = makeSucrasePlugin({}, sucrase);
41+
const transpilePlugin = makeEsbuildPlugin(esbuild);
4342
const debugBuildStatementReplacePlugin = makeDebugBuildStatementReplacePlugin();
44-
const cleanupPlugin = makeCleanupPlugin();
4543
const rrwebBuildPlugin = makeRrwebBuildPlugin({
4644
excludeShadowDom: undefined,
4745
excludeIframe: undefined,
@@ -104,7 +102,7 @@ export function makeBaseNPMConfig(options = {}) {
104102
},
105103
},
106104

107-
plugins: [nodeResolvePlugin, sucrasePlugin, debugBuildStatementReplacePlugin, rrwebBuildPlugin, cleanupPlugin],
105+
plugins: [nodeResolvePlugin, transpilePlugin, debugBuildStatementReplacePlugin, rrwebBuildPlugin],
108106

109107
// don't include imported modules from outside the package in the final output
110108
// also treat subpath exports (e.g. `@sentry/core/browser`) as external
@@ -154,8 +152,9 @@ export function makeNPMConfigVariants(baseConfig, options = {}) {
154152
output: {
155153
format: 'esm',
156154
dir: path.join(baseConfig.output.dir, 'esm/prod'),
157-
plugins: [makeProductionReplacePlugin(), makePackageNodeEsm()],
155+
plugins: [makePackageNodeEsm()],
158156
},
157+
plugins: [makeProductionReplacePlugin()],
159158
});
160159
} else {
161160
variantSpecificConfigs.push({
@@ -168,7 +167,13 @@ export function makeNPMConfigVariants(baseConfig, options = {}) {
168167
}
169168
}
170169

171-
return variantSpecificConfigs.map(variant => deepMerge(baseConfig, variant));
170+
return variantSpecificConfigs.map(variant =>
171+
// Plugin arrays must be merged in the right order or the build silently misbehaves
172+
// (e.g. esbuild strips dev-mode marker comments before the replace plugin can act).
173+
deepMerge(baseConfig, variant, {
174+
customMerge: key => (key === 'plugins' ? mergePlugins : undefined),
175+
}),
176+
);
172177
}
173178

174179
/**

dev-packages/rollup-utils/plugins/bundlePlugins.mjs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export function makeLicensePlugin(title) {
4646
* 'false`
4747
*/
4848
export function makeIsDebugBuildPlugin(includeDebugging) {
49-
return replace({
49+
const plugin = replace({
5050
// TODO `preventAssignment` will default to true in version 5.x of the replace plugin, at which point we can get rid
5151
// of this. (It actually makes no difference in this case whether it's true or false, since we never assign to
5252
// `__SENTRY_DEBUG__`, but if we don't give it a value, it will spam with warnings.)
@@ -58,16 +58,28 @@ export function makeIsDebugBuildPlugin(includeDebugging) {
5858
__SENTRY_DEBUG__: includeDebugging,
5959
},
6060
});
61+
plugin.name = 'replace-debug-flags';
62+
return plugin;
6163
}
6264

65+
/**
66+
* Replaces the comment marker `/*! __SENTRY_SDK_SOURCE__ *\/` in core's `getSDKSource()` with a
67+
* `return '<source>';` statement so the bundle reports the correct distribution channel.
68+
*
69+
* The marker uses the `/*! ... *\/` legal-comment syntax so it survives esbuild's transpile
70+
* (esbuild strips ordinary block comments). The plugin sort order in utils.mjs also pins
71+
* this name before `esbuild`, in case it ever runs on un-transpiled source directly.
72+
*/
6373
export function makeSetSDKSourcePlugin(sdkSource) {
64-
return replace({
74+
const plugin = replace({
6575
preventAssignment: false,
6676
delimiters: ['', ''],
6777
values: {
68-
'/* __SENTRY_SDK_SOURCE__ */': `return ${JSON.stringify(sdkSource)};`,
78+
'/*! __SENTRY_SDK_SOURCE__ */': `return ${JSON.stringify(sdkSource)};`,
6979
},
7080
});
81+
plugin.name = 'replace-sdk-source';
82+
return plugin;
7183
}
7284

7385
/**
@@ -77,13 +89,15 @@ export function makeSetSDKSourcePlugin(sdkSource) {
7789
* @returns An instance of the `replace` plugin to do the replacement of the magic string with `true` or 'false`
7890
*/
7991
export function makeBrowserBuildPlugin(isBrowserBuild) {
80-
return replace({
92+
const plugin = replace({
8193
// TODO This will be the default in the next version of the `replace` plugin
8294
preventAssignment: true,
8395
values: {
8496
__SENTRY_BROWSER_BUNDLE__: isBrowserBuild,
8597
},
8698
});
99+
plugin.name = 'replace-browser-bundle-flag';
100+
return plugin;
87101
}
88102

89103
// `terser` options reference: https://github.com/terser/terser#api-reference

dev-packages/rollup-utils/plugins/npmPlugins.mjs

Lines changed: 50 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -2,37 +2,50 @@
22
* Rollup plugin hooks docs: https://rollupjs.org/guide/en/#build-hooks and
33
* https://rollupjs.org/guide/en/#output-generation-hooks
44
*
5-
* Cleanup plugin docs: https://github.com/aMarCruz/rollup-plugin-cleanup
5+
* esbuild plugin docs: https://github.com/egoist/rollup-plugin-esbuild
66
* Replace plugin docs: https://github.com/rollup/plugins/tree/master/packages/replace
7-
* Sucrase plugin docs: https://github.com/rollup/plugins/tree/master/packages/sucrase
87
*/
98

109
import json from '@rollup/plugin-json';
1110
import replace from '@rollup/plugin-replace';
12-
import cleanup from 'rollup-plugin-cleanup';
13-
import sucrase from './vendor/sucrase-plugin.mjs';
11+
import esbuild from 'rollup-plugin-esbuild';
1412

1513
/**
16-
* Create a plugin to transpile TS syntax using `sucrase`.
14+
* Create a plugin to transpile TS/JSX syntax using `esbuild`.
1715
*
18-
* @returns An instance of the `@rollup/plugin-sucrase` plugin
16+
* `target: 'es2020'` keeps ES2020-native syntax (`?.`, `??`, optional catch binding) and
17+
* downlevels everything newer (logical assignment, numeric separators, class private
18+
* fields, static class blocks, ...).
19+
*
20+
* `esbuildOptions` are forwarded to `rollup-plugin-esbuild` verbatim and can override
21+
* any of the pinned defaults (e.g. JSX-related keys like `jsxFactory` / `jsxFragment`
22+
* for packages that use a non-React pragma).
1923
*/
20-
export function makeSucrasePlugin(options = {}, sucraseOptions = {}) {
21-
return sucrase(
22-
{
23-
// Required for bundling OTEL code properly
24-
exclude: ['**/*.json'],
25-
...options,
26-
},
27-
{
28-
transforms: ['typescript', 'jsx'],
29-
// We use a custom forked version of sucrase,
30-
// where there is a new option `disableES2019Transforms`
31-
disableESTransforms: false,
32-
disableES2019Transforms: true,
33-
...sucraseOptions,
24+
export function makeEsbuildPlugin(esbuildOptions = {}) {
25+
const plugin = esbuild({
26+
// `.json` is handled by the JSON plugin further down the pipeline.
27+
exclude: ['**/*.json'],
28+
// ES2020 is our floor — keeps `?.`/`??` native, downlevels everything newer.
29+
target: 'es2020',
30+
// Don't read per-package tsconfig (they vary and can pull in unrelated settings).
31+
// Pin only the compilerOptions that affect codegen.
32+
tsconfig: false,
33+
tsconfigRaw: {
34+
compilerOptions: {
35+
// Match the project tsconfig's effective behavior at target=es2020: class
36+
// field initializers compile to `this.x = v` (set semantics), not via the
37+
// `Object.defineProperty`-based `__publicField` helper esbuild emits by
38+
// default. This is what tsc itself outputs at this target.
39+
useDefineForClassFields: false,
40+
},
3441
},
35-
);
42+
sourceMap: true,
43+
...esbuildOptions,
44+
});
45+
46+
// Force a stable plugin name so the plugin sort order in utils.mjs can target it.
47+
plugin.name = 'esbuild';
48+
return plugin;
3649
}
3750

3851
export function makeJsonPlugin() {
@@ -89,23 +102,6 @@ export function makeDebuggerPlugin(hookName) {
89102
};
90103
}
91104

92-
/**
93-
* Create a plugin to clean up output files by:
94-
* - Converting line endings unix line endings
95-
* - Removing consecutive empty lines
96-
*
97-
* @returns A `rollup-plugin-cleanup` instance.
98-
*/
99-
export function makeCleanupPlugin() {
100-
return cleanup({
101-
// line endings are unix-ized by default
102-
comments: 'all', // comments to keep
103-
compactComments: 'false', // don't remove blank lines in multi-line comments
104-
maxEmptyLines: 1,
105-
extensions: ['js', 'jsx', 'ts', 'tsx'],
106-
});
107-
}
108-
109105
/**
110106
* Creates a plugin to replace all instances of "__DEBUG_BUILD__" with a safe statement that
111107
* a) evaluates to `true`
@@ -114,28 +110,30 @@ export function makeCleanupPlugin() {
114110
* @returns A `@rollup/plugin-replace` instance.
115111
*/
116112
export function makeDebugBuildStatementReplacePlugin() {
117-
return replace({
113+
const plugin = replace({
118114
preventAssignment: false,
119115
values: {
120116
__DEBUG_BUILD__: "(typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__)",
121117
},
122118
});
119+
plugin.name = 'replace-debug-build-statement';
120+
return plugin;
123121
}
124122

125123
export function makeProductionReplacePlugin() {
126-
const pattern = /\/\* rollup-include-development-only \*\/[\s\S]*?\/\* rollup-include-development-only-end \*\/\s*/g;
127-
128-
function stripDevBlocks(code) {
129-
if (!code) return null;
130-
if (!code.includes('rollup-include-development-only')) return null;
131-
const replaced = code.replace(pattern, '');
132-
return { code: replaced, map: null };
133-
}
124+
// Markers use the `/*! ... */` legal-comment syntax so esbuild preserves them through
125+
// transpile. We still run as a `transform` (per-module) hook rather than `renderChunk`:
126+
// the block typically uses imports declared at the module top, and stripping it before
127+
// rollup analyses module-graph imports lets those now-unused imports be tree-shaken away.
128+
// The plugin sort order in utils.mjs pins this before `esbuild`.
129+
const pattern =
130+
/\/\*! rollup-include-development-only \*\/[\s\S]*?\/\*! rollup-include-development-only-end \*\/\s*/g;
134131

135132
return {
136133
name: 'remove-dev-mode-blocks',
137-
renderChunk(code) {
138-
return stripDevBlocks(code);
134+
transform(code) {
135+
if (!code.includes('rollup-include-development-only')) return null;
136+
return { code: code.replace(pattern, ''), map: null };
139137
},
140138
};
141139
}
@@ -159,8 +157,10 @@ export function makeRrwebBuildPlugin({ excludeShadowDom, excludeIframe } = {}) {
159157
values['__RRWEB_EXCLUDE_IFRAME__'] = excludeIframe;
160158
}
161159

162-
return replace({
160+
const plugin = replace({
163161
preventAssignment: true,
164162
values,
165163
});
164+
plugin.name = 'replace-rrweb-build-flags';
165+
return plugin;
166166
}

dev-packages/rollup-utils/plugins/vendor/sucrase-plugin.mjs

Lines changed: 0 additions & 79 deletions
This file was deleted.

0 commit comments

Comments
 (0)