Skip to content

feat(build): Add rollup config for Node bundles #5142

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 5 commits into from
May 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/browser/rollup.bundle.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ const builds = [];

['es5', 'es6'].forEach(jsVersion => {
const baseBundleConfig = makeBaseBundleConfig({
bundleType: 'standalone',
input: 'src/index.ts',
isAddOn: false,
jsVersion,
licenseTitle: '@sentry/browser',
outputFileBase: `bundles/bundle${jsVersion === 'es5' ? '.es5' : ''}`,
Expand Down
2 changes: 1 addition & 1 deletion packages/integrations/rollup.bundle.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ const file = process.env.INTEGRATION_FILE;
const jsVersion = process.env.JS_VERSION;

const baseBundleConfig = makeBaseBundleConfig({
bundleType: 'addon',
input: `src/${file}`,
isAddOn: true,
jsVersion,
licenseTitle: '@sentry/integrations',
outputFileBase: `bundles/${file.replace('.ts', '')}${jsVersion === 'ES5' ? '.es5' : ''}`,
Expand Down
2 changes: 1 addition & 1 deletion packages/tracing/rollup.bundle.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ const builds = [];

['es5', 'es6'].forEach(jsVersion => {
const baseBundleConfig = makeBaseBundleConfig({
bundleType: 'standalone',
input: 'src/index.bundle.ts',
isAddOn: false,
jsVersion,
licenseTitle: '@sentry/tracing & @sentry/browser',
outputFileBase: `bundles/bundle.tracing${jsVersion === 'es5' ? '.es5' : ''}`,
Expand Down
2 changes: 1 addition & 1 deletion packages/vue/rollup.bundle.config.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { makeBaseBundleConfig, makeBundleConfigVariants } from '../../rollup/index.js';

const baseBundleConfig = makeBaseBundleConfig({
bundleType: 'standalone',
input: 'src/index.bundle.ts',
isAddOn: false,
jsVersion: 'es6',
licenseTitle: '@sentry/vue',
outputFileBase: 'bundle.vue',
Expand Down
2 changes: 1 addition & 1 deletion packages/wasm/rollup.bundle.config.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { makeBaseBundleConfig, makeBundleConfigVariants } from '../../rollup/index.js';

const baseBundleConfig = makeBaseBundleConfig({
bundleType: 'addon',
input: 'src/index.ts',
isAddOn: true,
jsVersion: 'es6',
licenseTitle: '@sentry/wasm',
outputFileBase: 'bundles/wasm',
Expand Down
67 changes: 40 additions & 27 deletions rollup/bundleHelpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
* Rollup config docs: https://rollupjs.org/guide/en/#big-list-of-options
*/

import assert from 'assert';
import { builtinModules } from 'module';

import deepMerge from 'deepmerge';

import {
makeBrowserBuildPlugin,
makeCommonJSPlugin,
makeIsDebugBuildPlugin,
makeLicensePlugin,
makeNodeResolvePlugin,
Expand All @@ -17,10 +18,10 @@ import {
makeTerserPlugin,
makeTSPlugin,
} from './plugins/index.js';
import { getLastElement, insertAt } from './utils.js';
import { mergePlugins } from './utils';

export function makeBaseBundleConfig(options) {
const { input, isAddOn, jsVersion, licenseTitle, outputFileBase } = options;
const { bundleType, input, jsVersion, licenseTitle, outputFileBase } = options;

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

// The `commonjs` plugin is the `esModuleInterop` of the bundling world. When used with `transformMixedEsModules`, it
// will include all dependencies, imported or required, in the final bundle. (Without it, CJS modules aren't included
// at all, and without `transformMixedEsModules`, they're only included if they're imported, not if they're required.)
const commonJSPlugin = makeCommonJSPlugin({ transformMixedEsModules: true });

// used by `@sentry/browser`, `@sentry/tracing`, and `@sentry/vue` (bundles which are a full SDK in and of themselves)
const standAloneBundleConfig = {
output: {
format: 'iife',
name: 'Sentry',
},
context: 'window',
plugins: [markAsBrowserBuildPlugin],
};

// used by `@sentry/integrations` and `@sentry/wasm` (bundles which need to be combined with a stand-alone SDK bundle)
Expand Down Expand Up @@ -69,6 +76,17 @@ export function makeBaseBundleConfig(options) {
// code to add after the CJS wrapper
footer: '}(window));',
},
plugins: [markAsBrowserBuildPlugin],
};

// used by `@sentry/serverless`, when creating the lambda layer
const nodeBundleConfig = {
output: {
format: 'cjs',
},
plugins: [commonJSPlugin],
// Don't bundle any of Node's core modules
external: builtinModules,
};

// used by all bundles
Expand All @@ -83,19 +101,21 @@ export function makeBaseBundleConfig(options) {
},
plugins:
jsVersion === 'es5'
? [tsPlugin, markAsBrowserBuildPlugin, nodeResolvePlugin, licensePlugin]
: [
sucrasePlugin,
removeBlankLinesPlugin,
removeESLintCommentsPlugin,
markAsBrowserBuildPlugin,
nodeResolvePlugin,
licensePlugin,
],
? [tsPlugin, nodeResolvePlugin, licensePlugin]
: [sucrasePlugin, removeBlankLinesPlugin, removeESLintCommentsPlugin, nodeResolvePlugin, licensePlugin],
treeshake: 'smallest',
};

return deepMerge(sharedBundleConfig, isAddOn ? addOnBundleConfig : standAloneBundleConfig);
const bundleTypeConfigMap = {
standalone: standAloneBundleConfig,
addon: addOnBundleConfig,
node: nodeBundleConfig,
};

return deepMerge(sharedBundleConfig, bundleTypeConfigMap[bundleType], {
// Plugins have to be in the correct order or everything breaks, so when merging we have to manually re-order them
customMerge: key => (key === 'plugins' ? mergePlugins : undefined),
});
}

/**
Expand All @@ -108,52 +128,45 @@ export function makeBaseBundleConfig(options) {
* @returns An array of versions of that config
*/
export function makeBundleConfigVariants(baseConfig) {
const { plugins: baseConfigPlugins } = baseConfig;
const includeDebuggingPlugin = makeIsDebugBuildPlugin(true);
const stripDebuggingPlugin = makeIsDebugBuildPlugin(false);
const terserPlugin = makeTerserPlugin();

// The license plugin has to be last, so it ends up after terser. Otherwise, terser will remove the license banner.
assert(
getLastElement(baseConfigPlugins).name === 'rollup-plugin-license',
`Last plugin in given options should be \`rollup-plugin-license\`. Found ${getLastElement(baseConfigPlugins).name}`,
);

// The additional options to use for each variant we're going to create
const variantSpecificConfigs = [
{
output: {
file: `${baseConfig.output.file}.js`,
},
plugins: insertAt(baseConfigPlugins, -2, includeDebuggingPlugin),
plugins: [includeDebuggingPlugin],
},
// This variant isn't particularly helpful for an SDK user, as it strips logging while making no other minification
// changes, so by default we don't create it. It is however very useful when debugging rollup's treeshaking, so it's
// left here for that purpose.
// {
// output: { file: `${baseConfig.output.file}.no-debug.js`,
// },
// plugins: insertAt(plugins, -2, stripDebuggingPlugin),
// plugins: [stripDebuggingPlugin],
// },
{
output: {
file: `${baseConfig.output.file}.min.js`,
},
plugins: insertAt(baseConfigPlugins, -2, stripDebuggingPlugin, terserPlugin),
plugins: [stripDebuggingPlugin, terserPlugin],
},
{
output: {
file: `${baseConfig.output.file}.debug.min.js`,
},
plugins: insertAt(baseConfigPlugins, -2, includeDebuggingPlugin, terserPlugin),
plugins: [terserPlugin],
},
];

return variantSpecificConfigs.map(variant =>
deepMerge(baseConfig, variant, {
// this makes it so that instead of concatenating the `plugin` properties of the two objects, the first value is
// just overwritten by the second value
arrayMerge: (first, second) => second,
// Merge the plugin arrays and make sure the end result is in the correct order. Everything else can use the
// default merge strategy.
customMerge: key => (key === 'plugins' ? mergePlugins : undefined),
}),
);
}
21 changes: 17 additions & 4 deletions rollup/plugins/bundlePlugins.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/**
* CommonJS plugin docs: https://github.com/rollup/plugins/tree/master/packages/commonjs
* License plugin docs: https://github.com/mjeanroy/rollup-plugin-license
* Replace plugin docs: https://github.com/rollup/plugins/tree/master/packages/replace
* Resolve plugin docs: https://github.com/rollup/plugins/tree/master/packages/node-resolve
Expand All @@ -7,6 +8,7 @@
* Typescript plugin docs: https://github.com/ezolenko/rollup-plugin-typescript2
*/

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

return license({
const plugin = license({
banner: {
content: `/*! <%= data.title %> <%= pkg.version %> (${commitHash}) | https://github.com/getsentry/sentry-javascript */`,
data: { title },
},
});

// give it a nicer name for later, when we'll need to sort the plugins
plugin.name = 'license';

return plugin;
}

/**
Expand Down Expand Up @@ -125,7 +132,7 @@ export function makeTSPlugin(jsVersion) {
// verbosity: 0,
};

return typescript(
const plugin = typescript(
deepMerge(baseTSPluginOptions, {
tsconfigOverride: {
compilerOptions: {
Expand All @@ -134,8 +141,14 @@ export function makeTSPlugin(jsVersion) {
},
}),
);

// give it a nicer name for later, when we'll need to sort the plugins
plugin.name = 'typescript';

return plugin;
}

// We don't pass this plugin any options, so no need to wrap it in another factory function, as `resolve` is itself
// already a factory function.
// We don't pass these plugins any options which need to be calculated or changed by us, so no need to wrap them in
// another factory function, as they are themselves already factory functions.
export { resolve as makeNodeResolvePlugin };
export { commonjs as makeCommonJSPlugin };
26 changes: 20 additions & 6 deletions rollup/utils.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
/**
* Helper functions to compensate for the fact that JS can't handle negative array indices very well
* Helper function to compensate for the fact that JS can't handle negative array indices very well
*/

export const getLastElement = array => {
return array[array.length - 1];
};

export const insertAt = (arr, index, ...insertees) => {
const newArr = [...arr];
// Add 1 to the array length so that the inserted element ends up in the right spot with respect to the length of the
Expand All @@ -14,3 +9,22 @@ export const insertAt = (arr, index, ...insertees) => {
newArr.splice(destinationIndex, 0, ...insertees);
return newArr;
};

/**
* Merge two arrays of plugins, making sure they're sorted in the correct order.
*/
export function mergePlugins(pluginsA, pluginsB) {
const plugins = [...pluginsA, ...pluginsB];
plugins.sort((a, b) => {
// Hacky way to make sure the ones we care about end up where they belong in the order. (Really the TS and sucrase
// plugins are tied - both should come first - but they're mutually exclusive, so they can come in arbitrary order
// here.)
const order = ['typescript', 'sucrase', '...', 'terser', 'license'];
const sortKeyA = order.includes(a.name) ? a.name : '...';
const sortKeyB = order.includes(b.name) ? b.name : '...';

return order.indexOf(sortKeyA) - order.indexOf(sortKeyB);
});

return plugins;
}