diff --git a/packages/browser/rollup.bundle.config.js b/packages/browser/rollup.bundle.config.js index 04ce4b438bc3..a17ef4f9d26d 100644 --- a/packages/browser/rollup.bundle.config.js +++ b/packages/browser/rollup.bundle.config.js @@ -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' : ''}`, diff --git a/packages/integrations/rollup.bundle.config.js b/packages/integrations/rollup.bundle.config.js index 9549a07b8f94..322f5d992267 100644 --- a/packages/integrations/rollup.bundle.config.js +++ b/packages/integrations/rollup.bundle.config.js @@ -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' : ''}`, diff --git a/packages/tracing/rollup.bundle.config.js b/packages/tracing/rollup.bundle.config.js index 091cb1f56958..0c3f4bdacf89 100644 --- a/packages/tracing/rollup.bundle.config.js +++ b/packages/tracing/rollup.bundle.config.js @@ -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' : ''}`, diff --git a/packages/vue/rollup.bundle.config.js b/packages/vue/rollup.bundle.config.js index 41bf0e7f659d..56fde34726cb 100644 --- a/packages/vue/rollup.bundle.config.js +++ b/packages/vue/rollup.bundle.config.js @@ -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', diff --git a/packages/wasm/rollup.bundle.config.js b/packages/wasm/rollup.bundle.config.js index e928d466049d..4ddef06d4e25 100644 --- a/packages/wasm/rollup.bundle.config.js +++ b/packages/wasm/rollup.bundle.config.js @@ -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', diff --git a/rollup/bundleHelpers.js b/rollup/bundleHelpers.js index 7a3d08dce715..be67ffbbdfb1 100644 --- a/rollup/bundleHelpers.js +++ b/rollup/bundleHelpers.js @@ -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, @@ -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(); @@ -30,6 +31,11 @@ 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: { @@ -37,6 +43,7 @@ export function makeBaseBundleConfig(options) { 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) @@ -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 @@ -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), + }); } /** @@ -108,24 +128,17 @@ 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 @@ -133,27 +146,27 @@ export function makeBundleConfigVariants(baseConfig) { // { // 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), }), ); } diff --git a/rollup/plugins/bundlePlugins.js b/rollup/plugins/bundlePlugins.js index c9939ca0b4bf..59f3fec01cd1 100644 --- a/rollup/plugins/bundlePlugins.js +++ b/rollup/plugins/bundlePlugins.js @@ -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 @@ -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'; @@ -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; } /** @@ -125,7 +132,7 @@ export function makeTSPlugin(jsVersion) { // verbosity: 0, }; - return typescript( + const plugin = typescript( deepMerge(baseTSPluginOptions, { tsconfigOverride: { compilerOptions: { @@ -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 }; diff --git a/rollup/utils.js b/rollup/utils.js index 00a97e991edf..6a7462788a47 100644 --- a/rollup/utils.js +++ b/rollup/utils.js @@ -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 @@ -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; +}