Skip to content

Commit 039fd78

Browse files
lobsterkatieAbhiPrasad
authored andcommitted
feat(build): Add rollup config for Node bundles (#5142)
Currently, we have the ability to create rollup configs for two types of bundles, both intended for use in the browser: standalone SDKs (like browser or vue) and SDK add-ons (like integrations). This adds a third option, namely bundles for Node. Though it's clearly a less-common use case, there are Node situations (like serverless functions) where having a single, fully-treeshaken file could be helpful. (Indeed, the reason for this addition is our AWS lambda layer, and a desire for a simpler and more robust way to make sure that it includes all of the necessary dependencies without a lot of unnecessary extras.) Adding this option required a small amount of refactoring - exchanging a boolean indicator of bundle type for a string option, and switching to a(n admittedly slightly hacky) sorting mechanism for ordering plugins rather than manually inserting specific plugins at various spots in the array. (This was necessary because adding the node config meant shifting plugins around in such a way that it became impossible to merge plugin arrays by simple concatenation. As a bonus side effect, it allowed the insertion logic to be removed as well.)
1 parent 66f1f79 commit 039fd78

File tree

8 files changed

+82
-42
lines changed

8 files changed

+82
-42
lines changed

packages/browser/rollup.bundle.config.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ const builds = [];
44

55
['es5', 'es6'].forEach(jsVersion => {
66
const baseBundleConfig = makeBaseBundleConfig({
7+
bundleType: 'standalone',
78
input: 'src/index.ts',
8-
isAddOn: false,
99
jsVersion,
1010
licenseTitle: '@sentry/browser',
1111
outputFileBase: `bundles/bundle${jsVersion === 'es5' ? '.es5' : ''}`,

packages/integrations/rollup.bundle.config.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ const file = process.env.INTEGRATION_FILE;
88
const jsVersion = process.env.JS_VERSION;
99

1010
const baseBundleConfig = makeBaseBundleConfig({
11+
bundleType: 'addon',
1112
input: `src/${file}`,
12-
isAddOn: true,
1313
jsVersion,
1414
licenseTitle: '@sentry/integrations',
1515
outputFileBase: `bundles/${file.replace('.ts', '')}${jsVersion === 'ES5' ? '.es5' : ''}`,

packages/tracing/rollup.bundle.config.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ const builds = [];
44

55
['es5', 'es6'].forEach(jsVersion => {
66
const baseBundleConfig = makeBaseBundleConfig({
7+
bundleType: 'standalone',
78
input: 'src/index.bundle.ts',
8-
isAddOn: false,
99
jsVersion,
1010
licenseTitle: '@sentry/tracing & @sentry/browser',
1111
outputFileBase: `bundles/bundle.tracing${jsVersion === 'es5' ? '.es5' : ''}`,

packages/vue/rollup.bundle.config.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { makeBaseBundleConfig, makeBundleConfigVariants } from '../../rollup/index.js';
22

33
const baseBundleConfig = makeBaseBundleConfig({
4+
bundleType: 'standalone',
45
input: 'src/index.bundle.ts',
5-
isAddOn: false,
66
jsVersion: 'es6',
77
licenseTitle: '@sentry/vue',
88
outputFileBase: 'bundle.vue',

packages/wasm/rollup.bundle.config.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { makeBaseBundleConfig, makeBundleConfigVariants } from '../../rollup/index.js';
22

33
const baseBundleConfig = makeBaseBundleConfig({
4+
bundleType: 'addon',
45
input: 'src/index.ts',
5-
isAddOn: true,
66
jsVersion: 'es6',
77
licenseTitle: '@sentry/wasm',
88
outputFileBase: 'bundles/wasm',

rollup/bundleHelpers.js

+40-27
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22
* Rollup config docs: https://rollupjs.org/guide/en/#big-list-of-options
33
*/
44

5-
import assert from 'assert';
5+
import { builtinModules } from 'module';
66

77
import deepMerge from 'deepmerge';
88

99
import {
1010
makeBrowserBuildPlugin,
11+
makeCommonJSPlugin,
1112
makeIsDebugBuildPlugin,
1213
makeLicensePlugin,
1314
makeNodeResolvePlugin,
@@ -17,10 +18,10 @@ import {
1718
makeTerserPlugin,
1819
makeTSPlugin,
1920
} from './plugins/index.js';
20-
import { getLastElement, insertAt } from './utils.js';
21+
import { mergePlugins } from './utils';
2122

2223
export function makeBaseBundleConfig(options) {
23-
const { input, isAddOn, jsVersion, licenseTitle, outputFileBase } = options;
24+
const { bundleType, input, jsVersion, licenseTitle, outputFileBase } = options;
2425

2526
const nodeResolvePlugin = makeNodeResolvePlugin();
2627
const sucrasePlugin = makeSucrasePlugin();
@@ -30,13 +31,19 @@ export function makeBaseBundleConfig(options) {
3031
const licensePlugin = makeLicensePlugin(licenseTitle);
3132
const tsPlugin = makeTSPlugin(jsVersion.toLowerCase());
3233

34+
// The `commonjs` plugin is the `esModuleInterop` of the bundling world. When used with `transformMixedEsModules`, it
35+
// will include all dependencies, imported or required, in the final bundle. (Without it, CJS modules aren't included
36+
// at all, and without `transformMixedEsModules`, they're only included if they're imported, not if they're required.)
37+
const commonJSPlugin = makeCommonJSPlugin({ transformMixedEsModules: true });
38+
3339
// used by `@sentry/browser`, `@sentry/tracing`, and `@sentry/vue` (bundles which are a full SDK in and of themselves)
3440
const standAloneBundleConfig = {
3541
output: {
3642
format: 'iife',
3743
name: 'Sentry',
3844
},
3945
context: 'window',
46+
plugins: [markAsBrowserBuildPlugin],
4047
};
4148

4249
// used by `@sentry/integrations` and `@sentry/wasm` (bundles which need to be combined with a stand-alone SDK bundle)
@@ -69,6 +76,17 @@ export function makeBaseBundleConfig(options) {
6976
// code to add after the CJS wrapper
7077
footer: '}(window));',
7178
},
79+
plugins: [markAsBrowserBuildPlugin],
80+
};
81+
82+
// used by `@sentry/serverless`, when creating the lambda layer
83+
const nodeBundleConfig = {
84+
output: {
85+
format: 'cjs',
86+
},
87+
plugins: [commonJSPlugin],
88+
// Don't bundle any of Node's core modules
89+
external: builtinModules,
7290
};
7391

7492
// used by all bundles
@@ -83,19 +101,21 @@ export function makeBaseBundleConfig(options) {
83101
},
84102
plugins:
85103
jsVersion === 'es5'
86-
? [tsPlugin, markAsBrowserBuildPlugin, nodeResolvePlugin, licensePlugin]
87-
: [
88-
sucrasePlugin,
89-
removeBlankLinesPlugin,
90-
removeESLintCommentsPlugin,
91-
markAsBrowserBuildPlugin,
92-
nodeResolvePlugin,
93-
licensePlugin,
94-
],
104+
? [tsPlugin, nodeResolvePlugin, licensePlugin]
105+
: [sucrasePlugin, removeBlankLinesPlugin, removeESLintCommentsPlugin, nodeResolvePlugin, licensePlugin],
95106
treeshake: 'smallest',
96107
};
97108

98-
return deepMerge(sharedBundleConfig, isAddOn ? addOnBundleConfig : standAloneBundleConfig);
109+
const bundleTypeConfigMap = {
110+
standalone: standAloneBundleConfig,
111+
addon: addOnBundleConfig,
112+
node: nodeBundleConfig,
113+
};
114+
115+
return deepMerge(sharedBundleConfig, bundleTypeConfigMap[bundleType], {
116+
// Plugins have to be in the correct order or everything breaks, so when merging we have to manually re-order them
117+
customMerge: key => (key === 'plugins' ? mergePlugins : undefined),
118+
});
99119
}
100120

101121
/**
@@ -108,52 +128,45 @@ export function makeBaseBundleConfig(options) {
108128
* @returns An array of versions of that config
109129
*/
110130
export function makeBundleConfigVariants(baseConfig) {
111-
const { plugins: baseConfigPlugins } = baseConfig;
112131
const includeDebuggingPlugin = makeIsDebugBuildPlugin(true);
113132
const stripDebuggingPlugin = makeIsDebugBuildPlugin(false);
114133
const terserPlugin = makeTerserPlugin();
115134

116-
// The license plugin has to be last, so it ends up after terser. Otherwise, terser will remove the license banner.
117-
assert(
118-
getLastElement(baseConfigPlugins).name === 'rollup-plugin-license',
119-
`Last plugin in given options should be \`rollup-plugin-license\`. Found ${getLastElement(baseConfigPlugins).name}`,
120-
);
121-
122135
// The additional options to use for each variant we're going to create
123136
const variantSpecificConfigs = [
124137
{
125138
output: {
126139
file: `${baseConfig.output.file}.js`,
127140
},
128-
plugins: insertAt(baseConfigPlugins, -2, includeDebuggingPlugin),
141+
plugins: [includeDebuggingPlugin],
129142
},
130143
// This variant isn't particularly helpful for an SDK user, as it strips logging while making no other minification
131144
// changes, so by default we don't create it. It is however very useful when debugging rollup's treeshaking, so it's
132145
// left here for that purpose.
133146
// {
134147
// output: { file: `${baseConfig.output.file}.no-debug.js`,
135148
// },
136-
// plugins: insertAt(plugins, -2, stripDebuggingPlugin),
149+
// plugins: [stripDebuggingPlugin],
137150
// },
138151
{
139152
output: {
140153
file: `${baseConfig.output.file}.min.js`,
141154
},
142-
plugins: insertAt(baseConfigPlugins, -2, stripDebuggingPlugin, terserPlugin),
155+
plugins: [stripDebuggingPlugin, terserPlugin],
143156
},
144157
{
145158
output: {
146159
file: `${baseConfig.output.file}.debug.min.js`,
147160
},
148-
plugins: insertAt(baseConfigPlugins, -2, includeDebuggingPlugin, terserPlugin),
161+
plugins: [terserPlugin],
149162
},
150163
];
151164

152165
return variantSpecificConfigs.map(variant =>
153166
deepMerge(baseConfig, variant, {
154-
// this makes it so that instead of concatenating the `plugin` properties of the two objects, the first value is
155-
// just overwritten by the second value
156-
arrayMerge: (first, second) => second,
167+
// Merge the plugin arrays and make sure the end result is in the correct order. Everything else can use the
168+
// default merge strategy.
169+
customMerge: key => (key === 'plugins' ? mergePlugins : undefined),
157170
}),
158171
);
159172
}

rollup/plugins/bundlePlugins.js

+17-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/**
2+
* CommonJS plugin docs: https://github.com/rollup/plugins/tree/master/packages/commonjs
23
* License plugin docs: https://github.com/mjeanroy/rollup-plugin-license
34
* Replace plugin docs: https://github.com/rollup/plugins/tree/master/packages/replace
45
* Resolve plugin docs: https://github.com/rollup/plugins/tree/master/packages/node-resolve
@@ -7,6 +8,7 @@
78
* Typescript plugin docs: https://github.com/ezolenko/rollup-plugin-typescript2
89
*/
910

11+
import commonjs from '@rollup/plugin-commonjs';
1012
import deepMerge from 'deepmerge';
1113
import license from 'rollup-plugin-license';
1214
import resolve from '@rollup/plugin-node-resolve';
@@ -23,12 +25,17 @@ import typescript from 'rollup-plugin-typescript2';
2325
export function makeLicensePlugin(title) {
2426
const commitHash = require('child_process').execSync('git rev-parse --short HEAD', { encoding: 'utf-8' }).trim();
2527

26-
return license({
28+
const plugin = license({
2729
banner: {
2830
content: `/*! <%= data.title %> <%= pkg.version %> (${commitHash}) | https://github.com/getsentry/sentry-javascript */`,
2931
data: { title },
3032
},
3133
});
34+
35+
// give it a nicer name for later, when we'll need to sort the plugins
36+
plugin.name = 'license';
37+
38+
return plugin;
3239
}
3340

3441
/**
@@ -125,7 +132,7 @@ export function makeTSPlugin(jsVersion) {
125132
// verbosity: 0,
126133
};
127134

128-
return typescript(
135+
const plugin = typescript(
129136
deepMerge(baseTSPluginOptions, {
130137
tsconfigOverride: {
131138
compilerOptions: {
@@ -134,8 +141,14 @@ export function makeTSPlugin(jsVersion) {
134141
},
135142
}),
136143
);
144+
145+
// give it a nicer name for later, when we'll need to sort the plugins
146+
plugin.name = 'typescript';
147+
148+
return plugin;
137149
}
138150

139-
// We don't pass this plugin any options, so no need to wrap it in another factory function, as `resolve` is itself
140-
// already a factory function.
151+
// We don't pass these plugins any options which need to be calculated or changed by us, so no need to wrap them in
152+
// another factory function, as they are themselves already factory functions.
141153
export { resolve as makeNodeResolvePlugin };
154+
export { commonjs as makeCommonJSPlugin };

rollup/utils.js

+20-6
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
11
/**
2-
* Helper functions to compensate for the fact that JS can't handle negative array indices very well
2+
* Helper function to compensate for the fact that JS can't handle negative array indices very well
33
*/
4-
5-
export const getLastElement = array => {
6-
return array[array.length - 1];
7-
};
8-
94
export const insertAt = (arr, index, ...insertees) => {
105
const newArr = [...arr];
116
// Add 1 to the array length so that the inserted element ends up in the right spot with respect to the length of the
@@ -14,3 +9,22 @@ export const insertAt = (arr, index, ...insertees) => {
149
newArr.splice(destinationIndex, 0, ...insertees);
1510
return newArr;
1611
};
12+
13+
/**
14+
* Merge two arrays of plugins, making sure they're sorted in the correct order.
15+
*/
16+
export function mergePlugins(pluginsA, pluginsB) {
17+
const plugins = [...pluginsA, ...pluginsB];
18+
plugins.sort((a, b) => {
19+
// Hacky way to make sure the ones we care about end up where they belong in the order. (Really the TS and sucrase
20+
// plugins are tied - both should come first - but they're mutually exclusive, so they can come in arbitrary order
21+
// here.)
22+
const order = ['typescript', 'sucrase', '...', 'terser', 'license'];
23+
const sortKeyA = order.includes(a.name) ? a.name : '...';
24+
const sortKeyB = order.includes(b.name) ? b.name : '...';
25+
26+
return order.indexOf(sortKeyA) - order.indexOf(sortKeyB);
27+
});
28+
29+
return plugins;
30+
}

0 commit comments

Comments
 (0)