Skip to content

Commit 3e23fc6

Browse files
committed
chore: Switch to rolldown
1 parent 03e18ae commit 3e23fc6

92 files changed

Lines changed: 1114 additions & 1013 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎dev-packages/e2e-tests/test-applications/nextjs-16/tests/worker-bundle.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ function readOrchestrionPluginGraphSources(): string[] {
5656
const entrySource = fs.readFileSync(pluginEntry, 'utf8');
5757
return [
5858
entrySource,
59-
...[...entrySource.matchAll(/require\('(\.\.?\/[^']+)'\)/g)].map(([, specifier]) =>
59+
...[...entrySource.matchAll(/require\(['"](\.\.?\/[^'"]+)['"]\)/g)].map(([, specifier]) =>
6060
fs.readFileSync(path.resolve(path.dirname(pluginEntry), specifier), 'utf8'),
6161
),
6262
];

‎dev-packages/node-integration-tests/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"scripts": {
1313
"build": "pnpm run build:transpile && pnpm run build:types",
1414
"build:dev": "pnpm run build",
15-
"build:transpile": "rollup -c rollup.npm.config.mjs",
15+
"build:transpile": "rolldown -c rollup.npm.config.mjs",
1616
"build:types": "tsc -p tsconfig.types.json",
1717
"clean": "rimraf -g suites/**/node_modules suites/**/tmp_* && pnpm run clean:script",
1818
"clean:script": "node scripts/clean.js",
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import * as Sentry from '@sentry/node';
2+
3+
// `Sentry.init()` synchronously calls `registerDiagnosticsChannelInjection()`, which reaches the
4+
// vendored orchestrion transformer. That chain is split so a bundler can tree-shake it (see
5+
// `makeCjsExportsSplitPlugin` in server-utils' rollup config); this asserts the split degrades to
6+
// "no channel injection" rather than throwing when the bundler drops it.
7+
Sentry.init({
8+
dsn: 'https://public@dsn.ingest.sentry.io/1337',
9+
tracesSampleRate: 0,
10+
autoSessionTracking: false,
11+
});
12+
13+
// eslint-disable-next-line no-console
14+
console.log(`SENTRY_NODE_INITIALIZED client=${Boolean(Sentry.getClient())}`);

‎dev-packages/node-integration-tests/suites/esbuild/test.ts‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,34 @@ describe('esbuild bundling', () => {
3232
rmSync(outDir, { recursive: true, force: true });
3333
}
3434
});
35+
36+
test('@sentry/node survives init() when bundled to ESM with tree-shaking', async () => {
37+
const outDir = mkdtempSync(join(tmpdir(), 'sentry-esbuild-esm-'));
38+
const outfile = join(outDir, 'bundle.mjs');
39+
40+
try {
41+
await build({
42+
entryPoints: [join(__dirname, 'app-init.ts')],
43+
outfile,
44+
platform: 'node',
45+
format: 'esm',
46+
bundle: true,
47+
// `@sentry/server-utils` is `sideEffects: false`, so this is where a bundler is free to
48+
// drop the vendored orchestrion transformer.
49+
treeShaking: true,
50+
logLevel: 'silent',
51+
});
52+
53+
const result = spawnSync('node', [outfile], { encoding: 'utf-8' });
54+
55+
// Dropping the transformer must not take `init()` with it: reading a tree-shaken CJS
56+
// container has to yield `undefined`, not throw on destructuring or on a call.
57+
expect(result.stderr).not.toContain('Cannot destructure');
58+
expect(result.stderr).not.toContain('is not a function');
59+
expect(result.status).toBe(0);
60+
expect(result.stdout).toContain('SENTRY_NODE_INITIALIZED client=true');
61+
} finally {
62+
rmSync(outDir, { recursive: true, force: true });
63+
}
64+
});
3565
});

‎dev-packages/rollup-utils/bundleHelpers.mjs‎

Lines changed: 36 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,50 @@
11
/**
2-
* Rollup config docs: https://rollupjs.org/guide/en/#big-list-of-options
2+
* Rolldown config docs: https://rolldown.rs/reference/config-options
33
*/
44

5-
import { builtinModules } from 'module';
5+
import * as fs from 'fs';
6+
import * as path from 'path';
67

78
import deepMerge from 'deepmerge';
89

910
import {
11+
makeBannerOptions,
1012
makeBrowserBuildPlugin,
11-
makeCommonJSPlugin,
12-
makeEsbuildPlugin,
1313
makeIsDebugBuildPlugin,
14-
makeLicensePlugin,
15-
makeNodeResolvePlugin,
14+
makeMinifierOptions,
1615
makeRrwebBuildPlugin,
1716
makeSetSDKSourcePlugin,
18-
makeTerserPlugin,
1917
} from './plugins/index.mjs';
20-
import { mergePlugins } from './utils.mjs';
18+
import { getNodeBuiltIns, mergePlugins, treeShakePreset } from './utils.mjs';
2119
import { makeProductionReplacePlugin } from './plugins/npmPlugins.mjs';
2220

2321
const BUNDLE_VARIANTS = ['.js', '.min.js', '.debug.min.js'];
2422

23+
const packageDotJSON = JSON.parse(fs.readFileSync(path.resolve(process.cwd(), './package.json'), { encoding: 'utf8' }));
24+
2525
export function makeBaseBundleConfig(options) {
26-
const { bundleType, entrypoints, licenseTitle, outputFileBase, packageSpecificConfig, esbuild } = options;
26+
const { bundleType, entrypoints, licenseTitle, outputFileBase, packageSpecificConfig } = options;
2727

28-
const nodeResolvePlugin = makeNodeResolvePlugin();
29-
const transpilePlugin = makeEsbuildPlugin(esbuild);
3028
const markAsBrowserBuildPlugin = makeBrowserBuildPlugin(true);
31-
const licensePlugin = makeLicensePlugin(licenseTitle);
29+
const banner = makeBannerOptions(licenseTitle, packageDotJSON.version);
3230
const rrwebBuildPlugin = makeRrwebBuildPlugin({
3331
excludeIframe: false,
3432
excludeShadowDom: false,
3533
});
3634
const productionReplacePlugin = makeProductionReplacePlugin();
3735

38-
// The `commonjs` plugin is the `esModuleInterop` of the bundling world. When used with `transformMixedEsModules`, it
39-
// will include all dependencies, imported or required, in the final bundle. (Without it, CJS modules aren't included
40-
// at all, and without `transformMixedEsModules`, they're only included if they're imported, not if they're required.)
41-
const commonJSPlugin = makeCommonJSPlugin({ transformMixedEsModules: true });
42-
4336
// used by `@sentry/browser`
4437
const standAloneBundleConfig = {
4538
output: {
39+
banner,
4640
format: 'iife',
4741
name: 'Sentry',
4842
intro: () => {
4943
return 'exports = window.Sentry || {};';
5044
},
5145
},
5246
context: 'window',
53-
plugins: [rrwebBuildPlugin, markAsBrowserBuildPlugin, licensePlugin],
47+
plugins: [rrwebBuildPlugin, markAsBrowserBuildPlugin],
5448
};
5549

5650
// used by `@sentry/wasm` & pluggable integrations from core/browser (bundles which need to be combined with a stand-alone SDK bundle)
@@ -61,7 +55,7 @@ export function makeBaseBundleConfig(options) {
6155
format: 'cjs',
6256

6357
// code to add before the CJS wrapper
64-
banner: '(function (__window) {',
58+
banner: `${banner}\n(function (__window) {`,
6559

6660
// code to add just inside the CJS wrapper, before any of the wrapped code
6761
intro: 'var exports = {};',
@@ -84,30 +78,41 @@ export function makeBaseBundleConfig(options) {
8478
// code to add after the CJS wrapper
8579
footer: '}(window));',
8680
},
87-
plugins: [rrwebBuildPlugin, markAsBrowserBuildPlugin, licensePlugin],
81+
plugins: [rrwebBuildPlugin, markAsBrowserBuildPlugin],
8882
};
8983

9084
const workerBundleConfig = {
9185
output: {
86+
banner,
9287
format: 'esm',
88+
minify: makeMinifierOptions(),
9389
},
94-
plugins: [commonJSPlugin, makeTerserPlugin(), licensePlugin],
9590
// Don't bundle any of Node's core modules
96-
external: builtinModules,
91+
external: getNodeBuiltIns(),
9792
};
9893

9994
const awsLambdaExtensionBundleConfig = {
10095
output: {
10196
format: 'esm',
97+
minify: makeMinifierOptions(),
10298
},
103-
plugins: [commonJSPlugin, makeIsDebugBuildPlugin(true), makeTerserPlugin()],
99+
plugins: [makeIsDebugBuildPlugin(true)],
104100
// Don't bundle any of Node's core modules
105-
external: builtinModules,
101+
external: getNodeBuiltIns(),
106102
};
107103

108104
// used by all bundles
109105
const sharedBundleConfig = {
110106
input: entrypoints,
107+
108+
// Point at the package's tsconfig so rolldown picks up its TypeScript & JSX settings.
109+
tsconfig: path.resolve(process.cwd(), './tsconfig.json'),
110+
111+
// ES2020 is our floor: keeps `?.`/`??` native and downlevels everything newer.
112+
transform: {
113+
target: 'es2020',
114+
},
115+
111116
output: {
112117
// a file extension will be added to this base value when we specify either a minified or non-minified build
113118
entryFileNames: outputFileBase,
@@ -116,8 +121,8 @@ export function makeBaseBundleConfig(options) {
116121
strict: false,
117122
esModule: false,
118123
},
119-
plugins: [productionReplacePlugin, transpilePlugin, nodeResolvePlugin],
120-
treeshake: 'smallest',
124+
plugins: [productionReplacePlugin],
125+
treeshake: treeShakePreset('smallest'),
121126
};
122127

123128
const bundleTypeConfigMap = {
@@ -147,7 +152,7 @@ export function makeBundleConfigVariants(baseConfig, options = {}) {
147152

148153
const includeDebuggingPlugin = makeIsDebugBuildPlugin(true);
149154
const stripDebuggingPlugin = makeIsDebugBuildPlugin(false);
150-
const terserPlugin = makeTerserPlugin();
155+
const minify = makeMinifierOptions();
151156
const setSdkSourcePlugin = makeSetSDKSourcePlugin('cdn');
152157

153158
// The additional options to use for each variant we're going to create.
@@ -162,15 +167,17 @@ export function makeBundleConfigVariants(baseConfig, options = {}) {
162167
'.min.js': {
163168
output: {
164169
entryFileNames: chunkInfo => `${baseConfig.output.entryFileNames(chunkInfo)}.min.js`,
170+
minify,
165171
},
166-
plugins: [stripDebuggingPlugin, setSdkSourcePlugin, terserPlugin],
172+
plugins: [stripDebuggingPlugin, setSdkSourcePlugin],
167173
},
168174

169175
'.debug.min.js': {
170176
output: {
171177
entryFileNames: chunkInfo => `${baseConfig.output.entryFileNames(chunkInfo)}.debug.min.js`,
178+
minify,
172179
},
173-
plugins: [includeDebuggingPlugin, setSdkSourcePlugin, terserPlugin],
180+
plugins: [includeDebuggingPlugin, setSdkSourcePlugin],
174181
},
175182
};
176183

‎dev-packages/rollup-utils/index.mjs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ export { plugins };
44

55
export * from './bundleHelpers.mjs';
66
export * from './npmHelpers.mjs';
7-
export { insertAt } from './utils.mjs';
7+
export { insertAt, treeShakePreset } from './utils.mjs';

‎dev-packages/rollup-utils/npmHelpers.mjs‎

Lines changed: 42 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,25 @@
11
// @ts-check
22

33
/**
4-
* Rollup config docs: https://rollupjs.org/guide/en/#big-list-of-options
4+
* Rolldown config docs: https://rolldown.rs/reference/config-options
55
*/
66

77
import * as fs from 'fs';
8-
import { builtinModules, isBuiltin } from 'module';
8+
import { isBuiltin } from 'module';
99
import * as path from 'path';
1010
import { fileURLToPath } from 'url';
1111

1212
import deepMerge from 'deepmerge';
1313

14-
import { defineConfig } from 'rollup';
14+
import { defineConfig } from 'rolldown';
1515
import {
1616
makeDebugBuildStatementReplacePlugin,
17-
makeEsbuildPlugin,
1817
makeEsmCjsReplacePlugin,
19-
makeNodeResolvePlugin,
2018
makeProductionReplacePlugin,
2119
makeRrwebBuildPlugin,
2220
} from './plugins/index.mjs';
2321
import { makePackageNodeEsm } from './plugins/make-esm-plugin.mjs';
24-
import { mergeExternals, mergePlugins } from './utils.mjs';
22+
import { getNodeBuiltIns, mergeExternals, mergePlugins } from './utils.mjs';
2523

2624
const __dirname = path.dirname(fileURLToPath(import.meta.url));
2725

@@ -97,20 +95,17 @@ export function makeBaseNPMConfig(options = {}) {
9795
entrypoints = ['src/index.ts'],
9896
hasBundles = false,
9997
packageSpecificConfig = {},
100-
esbuild = {},
10198
bundledBuiltins = [],
10299
} = options;
103100

104-
const nodeResolvePlugin = makeNodeResolvePlugin();
105-
const transpilePlugin = makeEsbuildPlugin(esbuild);
106101
const debugBuildStatementReplacePlugin = makeDebugBuildStatementReplacePlugin();
107102
const rrwebBuildPlugin = makeRrwebBuildPlugin({
108103
excludeShadowDom: undefined,
109104
excludeIframe: undefined,
110105
});
111106

112107
const deps = [
113-
...builtinModules.filter(m => !bundledBuiltins.includes(m)),
108+
...getNodeBuiltIns(bundledBuiltins),
114109
...Object.keys(packageDotJSON.dependencies || {}),
115110
...Object.keys(packageDotJSON.peerDependencies || {}),
116111
...Object.keys(packageDotJSON.optionalDependencies || {}),
@@ -119,6 +114,31 @@ export function makeBaseNPMConfig(options = {}) {
119114
const defaultBaseConfig = {
120115
input: entrypoints,
121116

117+
// Point at the package's tsconfig so rolldown picks up its TypeScript & JSX settings.
118+
tsconfig: path.resolve(process.cwd(), './tsconfig.json'),
119+
120+
// NOTE: we deliberately leave `platform` unset so rolldown infers it from the output format
121+
// ('node' for cjs, 'browser' for everything else). Every explicit value breaks something:
122+
// 'node' injects `import "node:module"` into each runtime chunk, which webpack rejects for
123+
// browser targets; 'neutral' rewrites `import.meta` to `{}`, so `createRequire(import.meta.url)`
124+
// gets `undefined`. Node-only packages that need the node platform for both halves set it
125+
// themselves (see server-utils and bundler-plugins).
126+
127+
// ES2020 is our floor: keeps `?.`/`??` native and downlevels everything newer. Packages that
128+
// need more (e.g. top-level await) raise it through `packageSpecificConfig`.
129+
transform: {
130+
target: 'es2020',
131+
132+
// The inferred 'browser' platform defines `process.env.NODE_ENV`, which folds every
133+
// `process.env.NODE_ENV !== 'development'` guard to a constant and lets DCE delete the
134+
// branch behind it. That silently dropped the orchestrion and source-map plugins from the
135+
// vite integrations' ESM output while the CJS half kept them. Mapping it to itself keeps
136+
// the runtime lookup, so the guard is evaluated by the consumer as it always was.
137+
define: {
138+
'process.env.NODE_ENV': 'process.env.NODE_ENV',
139+
},
140+
},
141+
122142
output: {
123143
// an appropriately-named directory will be added to this base value when we specify either a cjs or esm build
124144
dir: hasBundles ? 'build/npm' : 'build',
@@ -130,6 +150,7 @@ export function makeBaseNPMConfig(options = {}) {
130150

131151
// output individual files rather than one big bundle
132152
preserveModules: true,
153+
preserveModulesRoot: 'src',
133154

134155
// Don't hoist imports into entrypoints
135156
// should be ignored when `preserveModules` is used,
@@ -139,8 +160,6 @@ export function makeBaseNPMConfig(options = {}) {
139160
// Allow wrappers or helper functions generated by rollup to use any ES2015 features
140161
generatedCode: {
141162
preset: 'es2015',
142-
// With arrow functions, Node can't see `export *` exports when ESM code imports our CJS files.
143-
arrowFunctions: false,
144163
},
145164

146165
// don't add `"use strict"` to the top of cjs files
@@ -154,19 +173,17 @@ export function makeBaseNPMConfig(options = {}) {
154173
// get: () => are.great,
155174
// });
156175
externalLiveBindings: false,
157-
158-
// Don't call `Object.freeze` on the results of `import * as someModule from '...'`
159-
// (We don't need it, so why waste the bytes?)
160-
freeze: false,
161-
162-
// Assume externals are ESM-shaped (`__esModule` + `.default`), which our own `@sentry/*`
163-
// packages satisfy via `esModule: 'if-default-prop'`. This keeps `import * as x` a live
164-
// reference to the real module rather than an `_interopNamespace` copy — instrumentation code
165-
// relies on that to monkey-patch modules like `fs` in place. Packages that pull in bare-CJS
166-
// third-party deps (no `.default`) override this per-module (see server-utils).
167-
interop: 'esModule',
168176
},
169177

178+
// NOTE: rolldown has no equivalent of rollup's `output.interop`. In the CJS build it wraps
179+
// namespace imports of externals as `x = __toESM(require('x'))`, and `__toESM` returns a copy
180+
// whose properties are getter-only forwarders. Reads pass through to the real module but writes
181+
// do not, so code that monkey-patches a module in place has to default-import it (`import x
182+
// from 'x'`, whose `.default` is the live `require()` result) rather than namespace-import it.
183+
// Rolldown errors on a statically visible `ns.foo = ...` (ASSIGN_TO_IMPORT), but it cannot see a
184+
// write made through a helper, and that one fails silently. See
185+
// `packages/node/src/integrations/fs/vendored/instrumentation.ts`.
186+
170187
treeshake: {
171188
moduleSideEffects: (id, external) => {
172189
if (external === false && ignoreSideEffects.test(id)) {
@@ -186,7 +203,7 @@ export function makeBaseNPMConfig(options = {}) {
186203
},
187204
},
188205

189-
plugins: [nodeResolvePlugin, transpilePlugin, debugBuildStatementReplacePlugin, rrwebBuildPlugin],
206+
plugins: [debugBuildStatementReplacePlugin, rrwebBuildPlugin],
190207

191208
// don't include imported modules from outside the package in the final output
192209
// also treat subpath exports (e.g. `@sentry/core/browser`) as external
@@ -248,8 +265,7 @@ export function makeNPMConfigVariants(baseConfig, options = {}) {
248265
}
249266

250267
return variantSpecificConfigs.map(variant =>
251-
// Plugin arrays must be merged in the right order or the build silently misbehaves
252-
// (e.g. esbuild strips dev-mode marker comments before the replace plugin can act).
268+
// Plugin arrays must be merged in the right order or the build silently misbehaves.
253269
deepMerge(baseConfig, variant, {
254270
customMerge: key => (key === 'plugins' ? mergePlugins : undefined),
255271
}),

0 commit comments

Comments
 (0)