From f45c1e40ed017ff4654b1a13f3acf14cb149d2d3 Mon Sep 17 00:00:00 2001 From: Justin Ridgewell Date: Sun, 21 Mar 2021 00:17:27 -0400 Subject: [PATCH 1/2] Add Source Map sourceRoot Support This adds the `--sourcemap-root`, `sourcemapRoot` (JS), and `SourcemapRoot` (Go) options to insert a `sourceRoot` field in any output source maps. When a `sourceRoot` exists on the map, any sources are resolved relative to it. This is particularly useful when hosting compiled code on a server, and pointing the source files to a GitHub repo. Eg, as [AMP does](https://cdn.ampproject.org/v0.mjs.map). --- cmd/esbuild/main.go | 2 ++ internal/bundler/linker.go | 5 +++++ internal/config/config.go | 1 + lib/common.ts | 2 ++ lib/types.ts | 1 + pkg/api/api.go | 2 ++ pkg/api/api_impl.go | 2 ++ pkg/cli/cli_impl.go | 8 ++++++++ scripts/js-api-tests.js | 29 +++++++++++++++++++++++++++++ 9 files changed, 52 insertions(+) diff --git a/cmd/esbuild/main.go b/cmd/esbuild/main.go index b398442fc05..2141408a4f8 100644 --- a/cmd/esbuild/main.go +++ b/cmd/esbuild/main.go @@ -85,6 +85,8 @@ var helpText = func(colors logger.Colors) string { --sourcefile=... Set the source file for the source map (for stdin) --sourcemap=external Do not link to the source map with a comment --sourcemap=inline Emit the source map with an inline data URL + --sourcemap-root=... The root path from which source files are looked up + in a source map. --sources-content=false Omit "sourcesContent" in generated source maps --tree-shaking=... Set to "ignore-annotations" to work with packages that have incorrect tree-shaking annotations diff --git a/internal/bundler/linker.go b/internal/bundler/linker.go index 6538f246172..9d91606593f 100644 --- a/internal/bundler/linker.go +++ b/internal/bundler/linker.go @@ -4841,6 +4841,11 @@ func (c *linkerContext) generateSourceMapForChunk( } j.AddString("]") + if c.options.SourceMapRoot != "" { + j.AddString(",\n \"sourceRoot\": ") + j.AddBytes(js_printer.QuoteForJSON(c.options.SourceMapRoot, c.options.ASCIIOnly)) + } + // Write the sourcesContent if !c.options.ExcludeSourcesContent { j.AddString(",\n \"sourcesContent\": [") diff --git a/internal/config/config.go b/internal/config/config.go index 7dbb39bdf4c..4e88e44cb77 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -237,6 +237,7 @@ type Options struct { NeedsMetafile bool SourceMap SourceMap + SourceMapRoot string ExcludeSourcesContent bool Stdin *StdinInfo diff --git a/lib/common.ts b/lib/common.ts index 5785e47fb18..1f09a839c4d 100644 --- a/lib/common.ts +++ b/lib/common.ts @@ -94,6 +94,7 @@ function pushLogFlags(flags: string[], options: CommonOptions, keys: OptionKeys, } function pushCommonFlags(flags: string[], options: CommonOptions, keys: OptionKeys): void { + let sourcemapRoot = getFlag(options, keys, 'sourcemapRoot', mustBeString); let sourcesContent = getFlag(options, keys, 'sourcesContent', mustBeBoolean); let target = getFlag(options, keys, 'target', mustBeStringOrArray); let format = getFlag(options, keys, 'format', mustBeString); @@ -110,6 +111,7 @@ function pushCommonFlags(flags: string[], options: CommonOptions, keys: OptionKe let pure = getFlag(options, keys, 'pure', mustBeArray); let keepNames = getFlag(options, keys, 'keepNames', mustBeBoolean); + if (sourcemapRoot !== void 0) flags.push(`--sourcemap-root=${sourcemapRoot}`); if (sourcesContent !== void 0) flags.push(`--sources-content=${sourcesContent}`); if (target) { if (Array.isArray(target)) flags.push(`--target=${Array.from(target).map(validateTarget).join(',')}`) diff --git a/lib/types.ts b/lib/types.ts index d7a5e316124..58cafb5d9b3 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -7,6 +7,7 @@ export type TreeShaking = true | 'ignore-annotations'; interface CommonOptions { sourcemap?: boolean | 'inline' | 'external' | 'both'; + sourcemapRoot?: string; sourcesContent?: boolean; format?: Format; diff --git a/pkg/api/api.go b/pkg/api/api.go index 2bb7f93cc3c..8386c978329 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -223,6 +223,7 @@ type BuildOptions struct { LogLevel LogLevel Sourcemap SourceMap + SourcemapRoot string SourcesContent SourcesContent Target Target @@ -318,6 +319,7 @@ type TransformOptions struct { LogLevel LogLevel Sourcemap SourceMap + SourcemapRoot string SourcesContent SourcesContent Target Target diff --git a/pkg/api/api_impl.go b/pkg/api/api_impl.go index 98a1bca31ce..b728a311425 100644 --- a/pkg/api/api_impl.go +++ b/pkg/api/api_impl.go @@ -737,6 +737,7 @@ func rebuildImpl( InjectedDefines: injectedDefines, Platform: validatePlatform(buildOpts.Platform), SourceMap: validateSourceMap(buildOpts.Sourcemap), + SourceMapRoot: buildOpts.SourcemapRoot, ExcludeSourcesContent: buildOpts.SourcesContent == SourcesContentExclude, MangleSyntax: buildOpts.MinifySyntax, RemoveWhitespace: buildOpts.MinifyWhitespace, @@ -1188,6 +1189,7 @@ func transformImpl(input string, transformOpts TransformOptions) TransformResult Defines: defines, InjectedDefines: injectedDefines, SourceMap: validateSourceMap(transformOpts.Sourcemap), + SourceMapRoot: transformOpts.SourcemapRoot, ExcludeSourcesContent: transformOpts.SourcesContent == SourcesContentExclude, OutputFormat: validateFormat(transformOpts.Format), GlobalName: validateGlobalName(log, transformOpts.GlobalName), diff --git a/pkg/cli/cli_impl.go b/pkg/cli/cli_impl.go index 8097180366f..0cccd06481a 100644 --- a/pkg/cli/cli_impl.go +++ b/pkg/cli/cli_impl.go @@ -159,6 +159,14 @@ func parseOptionsImpl( } hasBareSourceMapFlag = false + case strings.HasPrefix(arg, "--sourcemap-root="): + sourceRoot := arg[len("--sourcemap-root="):] + if buildOpts != nil { + buildOpts.SourcemapRoot = sourceRoot + } else { + transformOpts.SourcemapRoot = sourceRoot + } + case strings.HasPrefix(arg, "--sources-content="): value := arg[len("--sources-content="):] var sourcesContent api.SourcesContent diff --git a/scripts/js-api-tests.js b/scripts/js-api-tests.js index a6a56f050e6..881cbdfdcc8 100644 --- a/scripts/js-api-tests.js +++ b/scripts/js-api-tests.js @@ -338,6 +338,29 @@ let buildTests = { assert.strictEqual(json.sourcesContent, void 0) }, + async sourceMapSourceRoot({ esbuild, testDir }) { + const input = path.join(testDir, 'in.js') + const output = path.join(testDir, 'out.js') + const content = 'exports.foo = 123' + await writeFileAsync(input, content) + await esbuild.build({ + entryPoints: [input], + outfile: output, + sourcemap: true, + sourcemapRoot: 'https://example.com/' + }) + const result = require(output) + assert.strictEqual(result.foo, 123) + const outputFile = await readFileAsync(output, 'utf8') + const match = /\/\/# sourceMappingURL=(.*)/.exec(outputFile) + assert.strictEqual(match[1], 'out.js.map') + const resultMap = await readFileAsync(output + '.map', 'utf8') + const json = JSON.parse(resultMap) + assert.strictEqual(json.version, 3) + assert.strictEqual(json.sources[0], path.basename(input)) + assert.strictEqual(json.sourceRoot, 'https://example.com/') + }, + async sourceMapWithDisabledFile({ esbuild, testDir }) { const input = path.join(testDir, 'in.js') const disabled = path.join(testDir, 'disabled.js') @@ -3152,6 +3175,12 @@ let transformTests = { await assertSourceMap(Buffer.from(base64.trim(), 'base64').toString(), 'afile.js') }, + async sourceMapRoot({ esbuild }) { + const { code, map } = await esbuild.transform(`let x`, { sourcemap: true, sourcefile: 'afile.js', sourcemapRoot: "https://example.com/" }) + assert.strictEqual(code, `let x;\n`) + assert.strictEqual(JSON.parse(map).sourceRoot, 'https://example.com/'); + }, + async numericLiteralPrinting({ esbuild }) { async function checkLiteral(text) { const { code } = await esbuild.transform(`return ${text}`, { minify: true }) From 716e207971a4d1fdf9466d61ce245e17f1fbfc9e Mon Sep 17 00:00:00 2001 From: Justin Ridgewell Date: Thu, 25 Mar 2021 01:24:33 -0400 Subject: [PATCH 2/2] Rename to sourceRoot --- cmd/esbuild/main.go | 2 +- internal/bundler/linker.go | 4 ++-- internal/config/config.go | 2 +- lib/common.ts | 4 ++-- lib/types.ts | 2 +- pkg/api/api.go | 4 ++-- pkg/api/api_impl.go | 4 ++-- pkg/cli/cli_impl.go | 8 ++++---- scripts/js-api-tests.js | 4 ++-- 9 files changed, 17 insertions(+), 17 deletions(-) diff --git a/cmd/esbuild/main.go b/cmd/esbuild/main.go index 2141408a4f8..a0c11a16b8d 100644 --- a/cmd/esbuild/main.go +++ b/cmd/esbuild/main.go @@ -85,7 +85,7 @@ var helpText = func(colors logger.Colors) string { --sourcefile=... Set the source file for the source map (for stdin) --sourcemap=external Do not link to the source map with a comment --sourcemap=inline Emit the source map with an inline data URL - --sourcemap-root=... The root path from which source files are looked up + --source-root=... The root path from which source files are looked up in a source map. --sources-content=false Omit "sourcesContent" in generated source maps --tree-shaking=... Set to "ignore-annotations" to work with packages diff --git a/internal/bundler/linker.go b/internal/bundler/linker.go index 9d91606593f..e3daef3899b 100644 --- a/internal/bundler/linker.go +++ b/internal/bundler/linker.go @@ -4841,9 +4841,9 @@ func (c *linkerContext) generateSourceMapForChunk( } j.AddString("]") - if c.options.SourceMapRoot != "" { + if c.options.SourceRoot != "" { j.AddString(",\n \"sourceRoot\": ") - j.AddBytes(js_printer.QuoteForJSON(c.options.SourceMapRoot, c.options.ASCIIOnly)) + j.AddBytes(js_printer.QuoteForJSON(c.options.SourceRoot, c.options.ASCIIOnly)) } // Write the sourcesContent diff --git a/internal/config/config.go b/internal/config/config.go index 4e88e44cb77..8c54ae7adab 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -237,7 +237,7 @@ type Options struct { NeedsMetafile bool SourceMap SourceMap - SourceMapRoot string + SourceRoot string ExcludeSourcesContent bool Stdin *StdinInfo diff --git a/lib/common.ts b/lib/common.ts index 1f09a839c4d..89bf879b9e6 100644 --- a/lib/common.ts +++ b/lib/common.ts @@ -94,7 +94,7 @@ function pushLogFlags(flags: string[], options: CommonOptions, keys: OptionKeys, } function pushCommonFlags(flags: string[], options: CommonOptions, keys: OptionKeys): void { - let sourcemapRoot = getFlag(options, keys, 'sourcemapRoot', mustBeString); + let sourceRoot = getFlag(options, keys, 'sourceRoot', mustBeString); let sourcesContent = getFlag(options, keys, 'sourcesContent', mustBeBoolean); let target = getFlag(options, keys, 'target', mustBeStringOrArray); let format = getFlag(options, keys, 'format', mustBeString); @@ -111,7 +111,7 @@ function pushCommonFlags(flags: string[], options: CommonOptions, keys: OptionKe let pure = getFlag(options, keys, 'pure', mustBeArray); let keepNames = getFlag(options, keys, 'keepNames', mustBeBoolean); - if (sourcemapRoot !== void 0) flags.push(`--sourcemap-root=${sourcemapRoot}`); + if (sourceRoot !== void 0) flags.push(`--source-root=${sourceRoot}`); if (sourcesContent !== void 0) flags.push(`--sources-content=${sourcesContent}`); if (target) { if (Array.isArray(target)) flags.push(`--target=${Array.from(target).map(validateTarget).join(',')}`) diff --git a/lib/types.ts b/lib/types.ts index 58cafb5d9b3..595c40d4832 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -7,7 +7,7 @@ export type TreeShaking = true | 'ignore-annotations'; interface CommonOptions { sourcemap?: boolean | 'inline' | 'external' | 'both'; - sourcemapRoot?: string; + sourceRoot?: string; sourcesContent?: boolean; format?: Format; diff --git a/pkg/api/api.go b/pkg/api/api.go index 8386c978329..2ce02a72101 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -223,7 +223,7 @@ type BuildOptions struct { LogLevel LogLevel Sourcemap SourceMap - SourcemapRoot string + SourceRoot string SourcesContent SourcesContent Target Target @@ -319,7 +319,7 @@ type TransformOptions struct { LogLevel LogLevel Sourcemap SourceMap - SourcemapRoot string + SourceRoot string SourcesContent SourcesContent Target Target diff --git a/pkg/api/api_impl.go b/pkg/api/api_impl.go index b728a311425..591501e484f 100644 --- a/pkg/api/api_impl.go +++ b/pkg/api/api_impl.go @@ -737,7 +737,7 @@ func rebuildImpl( InjectedDefines: injectedDefines, Platform: validatePlatform(buildOpts.Platform), SourceMap: validateSourceMap(buildOpts.Sourcemap), - SourceMapRoot: buildOpts.SourcemapRoot, + SourceRoot: buildOpts.SourceRoot, ExcludeSourcesContent: buildOpts.SourcesContent == SourcesContentExclude, MangleSyntax: buildOpts.MinifySyntax, RemoveWhitespace: buildOpts.MinifyWhitespace, @@ -1189,7 +1189,7 @@ func transformImpl(input string, transformOpts TransformOptions) TransformResult Defines: defines, InjectedDefines: injectedDefines, SourceMap: validateSourceMap(transformOpts.Sourcemap), - SourceMapRoot: transformOpts.SourcemapRoot, + SourceRoot: transformOpts.SourceRoot, ExcludeSourcesContent: transformOpts.SourcesContent == SourcesContentExclude, OutputFormat: validateFormat(transformOpts.Format), GlobalName: validateGlobalName(log, transformOpts.GlobalName), diff --git a/pkg/cli/cli_impl.go b/pkg/cli/cli_impl.go index 0cccd06481a..446aeda8bad 100644 --- a/pkg/cli/cli_impl.go +++ b/pkg/cli/cli_impl.go @@ -159,12 +159,12 @@ func parseOptionsImpl( } hasBareSourceMapFlag = false - case strings.HasPrefix(arg, "--sourcemap-root="): - sourceRoot := arg[len("--sourcemap-root="):] + case strings.HasPrefix(arg, "--source-root="): + sourceRoot := arg[len("--source-root="):] if buildOpts != nil { - buildOpts.SourcemapRoot = sourceRoot + buildOpts.SourceRoot = sourceRoot } else { - transformOpts.SourcemapRoot = sourceRoot + transformOpts.SourceRoot = sourceRoot } case strings.HasPrefix(arg, "--sources-content="): diff --git a/scripts/js-api-tests.js b/scripts/js-api-tests.js index 881cbdfdcc8..57fa3996126 100644 --- a/scripts/js-api-tests.js +++ b/scripts/js-api-tests.js @@ -347,7 +347,7 @@ let buildTests = { entryPoints: [input], outfile: output, sourcemap: true, - sourcemapRoot: 'https://example.com/' + sourceRoot: 'https://example.com/' }) const result = require(output) assert.strictEqual(result.foo, 123) @@ -3176,7 +3176,7 @@ let transformTests = { }, async sourceMapRoot({ esbuild }) { - const { code, map } = await esbuild.transform(`let x`, { sourcemap: true, sourcefile: 'afile.js', sourcemapRoot: "https://example.com/" }) + const { code, map } = await esbuild.transform(`let x`, { sourcemap: true, sourcefile: 'afile.js', sourceRoot: "https://example.com/" }) assert.strictEqual(code, `let x;\n`) assert.strictEqual(JSON.parse(map).sourceRoot, 'https://example.com/'); },