diff --git a/cli/help.md b/cli/help.md index e687b59fb0c..5918065a02f 100644 --- a/cli/help.md +++ b/cli/help.md @@ -54,6 +54,7 @@ Basic options: --no-sanitizeFileName Do not replace invalid characters in file names --shimMissingExports Create shim variables for missing exports --silent Don't print warnings +--sourcemapBaseUrl Emit absolute sourcemap URLs with given base --sourcemapExcludeSources Do not include source code in source maps --sourcemapFile Specify bundle position for source maps --stdin=ext Specify file extension used for stdin input diff --git a/docs/01-command-line-reference.md b/docs/01-command-line-reference.md index 703a221b7bc..60dd8db5182 100755 --- a/docs/01-command-line-reference.md +++ b/docs/01-command-line-reference.md @@ -90,6 +90,7 @@ export default { preserveModules, preserveModulesRoot, sourcemap, + sourcemapBaseUrl, sourcemapExcludeSources, sourcemapFile, sourcemapPathTransform, @@ -360,6 +361,7 @@ Many options have command line equivalents. In those cases, any arguments passed --no-sanitizeFileName Do not replace invalid characters in file names --shimMissingExports Create shim variables for missing exports --silent Don't print warnings +--sourcemapBaseUrl Emit absolute sourcemap URLs with given base --sourcemapExcludeSources Do not include source code in source maps --sourcemapFile Specify bundle position for source maps --stdin=ext Specify file extension used for stdin input diff --git a/docs/999-big-list-of-options.md b/docs/999-big-list-of-options.md index 48d213d443f..cd4d760ddaf 100755 --- a/docs/999-big-list-of-options.md +++ b/docs/999-big-list-of-options.md @@ -1075,11 +1075,17 @@ Type: `boolean | 'inline' | 'hidden'`
CLI: `-m`/`--sourcemap`/`--no-sourcema If `true`, a separate sourcemap file will be created. If `"inline"`, the sourcemap will be appended to the resulting `output` file as a data URI. `"hidden"` works like `true` except that the corresponding sourcemap comments in the bundled files are suppressed. +### output.sourcemapBaseUrl + +Type: `string`
CLI: `--sourcemapBaseUrl ` + +By default, sourcemap files generated by Rollup uses relative URLs to reference the files they describe. By providing an absolute base URL, e.g. `https://example.com`, sourcemaps will use absolute URLs instead. + #### output.sourcemapExcludeSources Type: `boolean`
CLI: `--sourcemapExcludeSources`/`--no-sourcemapExcludeSources`
Default: `false` -If `true`, the actual code of the sources will not be added to the sourcemaps making them considerably smaller. +If `true`, the actual code of the sources will not be added to the sourcemaps, making them considerably smaller. #### output.sourcemapFile diff --git a/src/rollup/rollup.ts b/src/rollup/rollup.ts index f1981f7f1d5..ed52b7dacc9 100644 --- a/src/rollup/rollup.ts +++ b/src/rollup/rollup.ts @@ -284,7 +284,11 @@ async function writeOutputFile( if (outputOptions.sourcemap === 'inline') { url = outputFile.map.toUrl(); } else { - url = `${basename(outputFile.fileName)}.map`; + const { sourcemapBaseUrl } = outputOptions; + const sourcemapFileName = `${basename(outputFile.fileName)}.map`; + url = sourcemapBaseUrl + ? new URL(sourcemapFileName, sourcemapBaseUrl).toString() + : sourcemapFileName; writeSourceMapPromise = fs.writeFile(`${fileName}.map`, outputFile.map.toString()); } if (outputOptions.sourcemap !== 'hidden') { diff --git a/src/rollup/types.d.ts b/src/rollup/types.d.ts index c88d53b4182..193e780e6e6 100644 --- a/src/rollup/types.d.ts +++ b/src/rollup/types.d.ts @@ -692,6 +692,7 @@ export interface OutputOptions { preserveModulesRoot?: string; sanitizeFileName?: boolean | ((fileName: string) => string); sourcemap?: boolean | 'inline' | 'hidden'; + sourcemapBaseUrl?: string; sourcemapExcludeSources?: boolean; sourcemapFile?: string; sourcemapPathTransform?: SourcemapPathTransformOption; @@ -739,6 +740,7 @@ export interface NormalizedOutputOptions { preserveModulesRoot: string | undefined; sanitizeFileName: (fileName: string) => string; sourcemap: boolean | 'inline' | 'hidden'; + sourcemapBaseUrl: string | undefined; sourcemapExcludeSources: boolean; sourcemapFile: string | undefined; sourcemapPathTransform: SourcemapPathTransformOption | undefined; diff --git a/src/utils/options/mergeOptions.ts b/src/utils/options/mergeOptions.ts index 613828ed36d..84674e98652 100644 --- a/src/utils/options/mergeOptions.ts +++ b/src/utils/options/mergeOptions.ts @@ -263,6 +263,7 @@ function mergeOutputOptions( preserveModulesRoot: getOption('preserveModulesRoot'), sanitizeFileName: getOption('sanitizeFileName'), sourcemap: getOption('sourcemap'), + sourcemapBaseUrl: getOption('sourcemapBaseUrl'), sourcemapExcludeSources: getOption('sourcemapExcludeSources'), sourcemapFile: getOption('sourcemapFile'), sourcemapPathTransform: getOption('sourcemapPathTransform'), diff --git a/src/utils/options/normalizeOutputOptions.ts b/src/utils/options/normalizeOutputOptions.ts index c23e8fae064..26974c88147 100644 --- a/src/utils/options/normalizeOutputOptions.ts +++ b/src/utils/options/normalizeOutputOptions.ts @@ -10,6 +10,7 @@ import { ensureArray } from '../ensureArray'; import { errInvalidExportOptionValue, errInvalidOption, error, warnDeprecation } from '../error'; import { resolve } from '../path'; import { sanitizeFileName as defaultSanitizeFileName } from '../sanitizeFileName'; +import { isValidUrl } from '../url'; import { generatedCodePresets, type GenericConfigObject, @@ -76,6 +77,7 @@ export function normalizeOutputOptions( ? id => id : defaultSanitizeFileName, sourcemap: config.sourcemap || false, + sourcemapBaseUrl: getSourcemapBaseUrl(config), sourcemapExcludeSources: config.sourcemapExcludeSources || false, sourcemapFile: config.sourcemapFile, sourcemapPathTransform: config.sourcemapPathTransform as @@ -471,3 +473,21 @@ const getNamespaceToStringTag = ( } return generatedCode.symbols || false; }; + +const getSourcemapBaseUrl = ( + config: OutputOptions +): NormalizedOutputOptions['sourcemapBaseUrl'] => { + const { sourcemapBaseUrl } = config; + if (sourcemapBaseUrl) { + if (isValidUrl(sourcemapBaseUrl)) { + return sourcemapBaseUrl; + } + return error( + errInvalidOption( + 'output.sourcemapBaseUrl', + 'outputsourcemapbaseurl', + `must be a valid URL, received ${JSON.stringify(sourcemapBaseUrl)}` + ) + ); + } +}; diff --git a/src/utils/url.ts b/src/utils/url.ts new file mode 100644 index 00000000000..f44fe9ed902 --- /dev/null +++ b/src/utils/url.ts @@ -0,0 +1,8 @@ +export function isValidUrl(url: string): boolean { + try { + new URL(url); + } catch (_) { + return false; + } + return true; +} diff --git a/test/function/samples/sourcemap-base-url-invalid/_config.js b/test/function/samples/sourcemap-base-url-invalid/_config.js new file mode 100644 index 00000000000..8a01d8b121e --- /dev/null +++ b/test/function/samples/sourcemap-base-url-invalid/_config.js @@ -0,0 +1,14 @@ +module.exports = { + description: 'throws for invalid sourcemapBaseUrl', + options: { + output: { + sourcemapBaseUrl: 'example.com' + } + }, + generateError: { + code: 'INVALID_OPTION', + message: + 'Invalid value for option "output.sourcemapBaseUrl" - must be a valid URL, received "example.com".', + url: 'https://rollupjs.org/guide/en/#outputsourcemapbaseurl' + } +}; diff --git a/test/function/samples/sourcemap-base-url-invalid/main.js b/test/function/samples/sourcemap-base-url-invalid/main.js new file mode 100644 index 00000000000..5c72ff35124 --- /dev/null +++ b/test/function/samples/sourcemap-base-url-invalid/main.js @@ -0,0 +1 @@ +console.log( 42 ); diff --git a/test/misc/optionList.js b/test/misc/optionList.js index e44f0166064..e0abbd63d5d 100644 --- a/test/misc/optionList.js +++ b/test/misc/optionList.js @@ -1,6 +1,6 @@ exports.input = 'acorn, acornInjectPlugins, cache, context, experimentalCacheExpiry, external, inlineDynamicImports, input, makeAbsoluteExternalsRelative, manualChunks, maxParallelFileReads, moduleContext, onwarn, perf, plugins, preserveEntrySignatures, preserveModules, preserveSymlinks, shimMissingExports, strictDeprecations, treeshake, watch'; exports.flags = - 'acorn, acornInjectPlugins, amd, assetFileNames, banner, c, cache, chunkFileNames, compact, config, configPlugin, context, d, dir, dynamicImportFunction, e, entryFileNames, environment, esModule, experimentalCacheExpiry, exports, extend, external, externalLiveBindings, f, failAfterWarnings, file, footer, format, freeze, g, generatedCode, globals, h, hoistTransitiveImports, i, indent, inlineDynamicImports, input, interop, intro, m, makeAbsoluteExternalsRelative, manualChunks, maxParallelFileReads, minifyInternalExports, moduleContext, n, name, namespaceToStringTag, noConflict, o, onwarn, outro, p, paths, perf, plugin, plugins, preferConst, preserveEntrySignatures, preserveModules, preserveModulesRoot, preserveSymlinks, sanitizeFileName, shimMissingExports, silent, sourcemap, sourcemapExcludeSources, sourcemapFile, stdin, strict, strictDeprecations, systemNullSetters, treeshake, v, validate, w, waitForBundleInput, watch'; + 'acorn, acornInjectPlugins, amd, assetFileNames, banner, c, cache, chunkFileNames, compact, config, configPlugin, context, d, dir, dynamicImportFunction, e, entryFileNames, environment, esModule, experimentalCacheExpiry, exports, extend, external, externalLiveBindings, f, failAfterWarnings, file, footer, format, freeze, g, generatedCode, globals, h, hoistTransitiveImports, i, indent, inlineDynamicImports, input, interop, intro, m, makeAbsoluteExternalsRelative, manualChunks, maxParallelFileReads, minifyInternalExports, moduleContext, n, name, namespaceToStringTag, noConflict, o, onwarn, outro, p, paths, perf, plugin, plugins, preferConst, preserveEntrySignatures, preserveModules, preserveModulesRoot, preserveSymlinks, sanitizeFileName, shimMissingExports, silent, sourcemap, sourcemapBaseUrl, sourcemapExcludeSources, sourcemapFile, stdin, strict, strictDeprecations, systemNullSetters, treeshake, v, validate, w, waitForBundleInput, watch'; exports.output = - 'amd, assetFileNames, banner, chunkFileNames, compact, dir, dynamicImportFunction, entryFileNames, esModule, exports, extend, externalLiveBindings, file, footer, format, freeze, generatedCode, globals, hoistTransitiveImports, indent, inlineDynamicImports, interop, intro, manualChunks, minifyInternalExports, name, namespaceToStringTag, noConflict, outro, paths, plugins, preferConst, preserveModules, preserveModulesRoot, sanitizeFileName, sourcemap, sourcemapExcludeSources, sourcemapFile, sourcemapPathTransform, strict, systemNullSetters, validate'; + 'amd, assetFileNames, banner, chunkFileNames, compact, dir, dynamicImportFunction, entryFileNames, esModule, exports, extend, externalLiveBindings, file, footer, format, freeze, generatedCode, globals, hoistTransitiveImports, indent, inlineDynamicImports, interop, intro, manualChunks, minifyInternalExports, name, namespaceToStringTag, noConflict, outro, paths, plugins, preferConst, preserveModules, preserveModulesRoot, sanitizeFileName, sourcemap, sourcemapBaseUrl, sourcemapExcludeSources, sourcemapFile, sourcemapPathTransform, strict, systemNullSetters, validate'; diff --git a/test/sourcemaps/samples/sourcemap-base-url/_config.js b/test/sourcemaps/samples/sourcemap-base-url/_config.js new file mode 100644 index 00000000000..2ae2b9d6036 --- /dev/null +++ b/test/sourcemaps/samples/sourcemap-base-url/_config.js @@ -0,0 +1,22 @@ +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); + +module.exports = { + description: 'adds a sourcemap base url', + options: { + output: { + sourcemapBaseUrl: 'https://example.com' + } + }, + test: (code, map, profile) => { + assert.equal(map.file, `bundle.${profile.format}.js`); + const bundlePath = path.join(__dirname, `_actual/bundle.${profile.format}.js`); + const bundledCode = fs.readFileSync(bundlePath, { encoding: 'utf8', flag: 'r' }); + const sourceMappingURL = bundledCode.split('\n').slice(-2)[0]; + assert.equal( + sourceMappingURL, + `//# sourceMappingURL=https://example.com/bundle.${profile.format}.js.map` + ); + } +}; diff --git a/test/sourcemaps/samples/sourcemap-base-url/main.js b/test/sourcemaps/samples/sourcemap-base-url/main.js new file mode 100644 index 00000000000..5c72ff35124 --- /dev/null +++ b/test/sourcemaps/samples/sourcemap-base-url/main.js @@ -0,0 +1 @@ +console.log( 42 );