Skip to content

Commit

Permalink
add "--sources-content=false" (#624)
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Dec 29, 2020
1 parent 97e317f commit 442f2a7
Show file tree
Hide file tree
Showing 11 changed files with 162 additions and 64 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@

If the working directory ended in `/`, the last path component would be incorrectly duplicated. This was the case when running esbuild with Yarn 2 (but not Yarn 1) and is problematic because some externally-facing directories reference the current working directory in plugins and in output files. The problem has now been fixed and the last path component is no longer duplicated in this case. This fix was contributed by [@remorses](https://github.com/remorses).

* Add an option to omit `sourcesContent` from generated source maps ([#624](https://github.com/evanw/esbuild/issues/624))

You can now pass `--sources-content=false` to omit the `sourcesContent` field from generated source maps. The field embeds the original source code inline in the source map and is the largest part of the source map. This is useful if you don't need the original source code and would like a smaller source map (e.g. you only care about stack traces and don't need the source code for debugging).

## 0.8.26

* Ensure the current working directory remains unique per `startService()` call
Expand Down
1 change: 1 addition & 0 deletions cmd/esbuild/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,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
--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
--tsconfig=... Use this tsconfig.json file instead of other ones
Expand Down
40 changes: 21 additions & 19 deletions internal/bundler/bundler.go
Original file line number Diff line number Diff line change
Expand Up @@ -1487,28 +1487,30 @@ func (b *Bundle) computeDataForSourceMapsInParallel(options *config.Options, rea
result := &results[sourceIndex]
result.lineOffsetTables = js_printer.GenerateLineOffsetTables(f.source.Contents, repr.ast.ApproximateLineCount)
sm := f.sourceMap
if sm == nil {
// Simple case: no nested source map
result.quotedContents = [][]byte{js_printer.QuoteForJSON(f.source.Contents, options.ASCIIOnly)}
} else {
// Complex case: nested source map
result.quotedContents = make([][]byte, len(sm.Sources))
nullContents := []byte("null")
for i := range sm.Sources {
// Missing contents become a "null" literal
quotedContents := nullContents
if i < len(sm.SourcesContent) {
if value := sm.SourcesContent[i]; value.Quoted != "" {
if options.ASCIIOnly && !isASCIIOnly(value.Quoted) {
// Re-quote non-ASCII values if output is ASCII-only
quotedContents = js_printer.QuoteForJSON(js_lexer.UTF16ToString(value.Value), options.ASCIIOnly)
} else {
// Otherwise just use the value directly from the input file
quotedContents = []byte(value.Quoted)
if !options.ExcludeSourcesContent {
if sm == nil {
// Simple case: no nested source map
result.quotedContents = [][]byte{js_printer.QuoteForJSON(f.source.Contents, options.ASCIIOnly)}
} else {
// Complex case: nested source map
result.quotedContents = make([][]byte, len(sm.Sources))
nullContents := []byte("null")
for i := range sm.Sources {
// Missing contents become a "null" literal
quotedContents := nullContents
if i < len(sm.SourcesContent) {
if value := sm.SourcesContent[i]; value.Quoted != "" {
if options.ASCIIOnly && !isASCIIOnly(value.Quoted) {
// Re-quote non-ASCII values if output is ASCII-only
quotedContents = js_printer.QuoteForJSON(js_lexer.UTF16ToString(value.Value), options.ASCIIOnly)
} else {
// Otherwise just use the value directly from the input file
quotedContents = []byte(value.Quoted)
}
}
}
result.quotedContents[i] = quotedContents
}
result.quotedContents[i] = quotedContents
}
}
waitGroup.Done()
Expand Down
26 changes: 18 additions & 8 deletions internal/bundler/linker.go
Original file line number Diff line number Diff line change
Expand Up @@ -4266,10 +4266,14 @@ func (c *linkerContext) generateSourceMapForChunk(

// Simple case: no nested source map
if file.sourceMap == nil {
var quotedContents []byte
if !c.options.ExcludeSourcesContent {
quotedContents = dataForSourceMaps[result.sourceIndex].quotedContents[0]
}
items = append(items, item{
path: file.source.KeyPath,
prettyPath: file.source.PrettyPath,
quotedContents: dataForSourceMaps[result.sourceIndex].quotedContents[0],
quotedContents: quotedContents,
})
continue
}
Expand All @@ -4288,10 +4292,14 @@ func (c *linkerContext) generateSourceMapForChunk(
path.Text = c.fs.Join(c.fs.Dir(file.source.KeyPath.Text), source)
}

var quotedContents []byte
if !c.options.ExcludeSourcesContent {
quotedContents = dataForSourceMaps[result.sourceIndex].quotedContents[i]
}
items = append(items, item{
path: path,
prettyPath: source,
quotedContents: dataForSourceMaps[result.sourceIndex].quotedContents[i],
quotedContents: quotedContents,
})
}
}
Expand All @@ -4317,14 +4325,16 @@ func (c *linkerContext) generateSourceMapForChunk(
j.AddString("]")

// Write the sourcesContent
j.AddString(",\n \"sourcesContent\": [")
for i, item := range items {
if i != 0 {
j.AddString(", ")
if !c.options.ExcludeSourcesContent {
j.AddString(",\n \"sourcesContent\": [")
for i, item := range items {
if i != 0 {
j.AddString(", ")
}
j.AddBytes(item.quotedContents)
}
j.AddBytes(item.quotedContents)
j.AddString("]")
}
j.AddString("]")

// Write the mappings
j.AddString(",\n \"mappings\": \"")
Expand Down
6 changes: 4 additions & 2 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,8 +216,10 @@ type Options struct {
// If present, metadata about the bundle is written as JSON here
AbsMetadataFile string

SourceMap SourceMap
Stdin *StdinInfo
SourceMap SourceMap
ExcludeSourcesContent bool

Stdin *StdinInfo
}

type InjectedFile struct {
Expand Down
2 changes: 2 additions & 0 deletions lib/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ function flagsForBuildOptions(
pushCommonFlags(flags, options, keys);

let sourcemap = getFlag(options, keys, 'sourcemap', mustBeStringOrBoolean);
let sourcesContent = getFlag(options, keys, 'sourcesContent', mustBeBoolean);
let bundle = getFlag(options, keys, 'bundle', mustBeBoolean);
let splitting = getFlag(options, keys, 'splitting', mustBeBoolean);
let metafile = getFlag(options, keys, 'metafile', mustBeString);
Expand All @@ -182,6 +183,7 @@ function flagsForBuildOptions(
checkForInvalidFlags(options, keys, `in ${callName}() call`);

if (sourcemap) flags.push(`--sourcemap${sourcemap === true ? '' : `=${sourcemap}`}`);
if (sourcesContent !== void 0) flags.push(`--sources-content=${sourcesContent}`);
if (bundle) flags.push('--bundle');
if (splitting) flags.push('--splitting');
if (metafile) flags.push(`--metafile=${metafile}`);
Expand Down
2 changes: 2 additions & 0 deletions lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ export type TreeShaking = true | 'ignore-annotations';

interface CommonOptions {
sourcemap?: boolean | 'inline' | 'external';
sourcesContent?: boolean;

format?: Format;
globalName?: string;
target?: string | string[];
Expand Down
19 changes: 15 additions & 4 deletions pkg/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,13 @@ const (
SourceMapExternal
)

type SourcesContent uint8

const (
SourcesContentInclude SourcesContent = iota
SourcesContentExclude
)

type Target uint8

const (
Expand Down Expand Up @@ -203,9 +210,11 @@ type BuildOptions struct {
ErrorLimit int
LogLevel LogLevel

Sourcemap SourceMap
Target Target
Engines []Engine
Sourcemap SourceMap
SourcesContent SourcesContent

Target Target
Engines []Engine

MinifyWhitespace bool
MinifyIdentifiers bool
Expand Down Expand Up @@ -281,7 +290,9 @@ type TransformOptions struct {
ErrorLimit int
LogLevel LogLevel

Sourcemap SourceMap
Sourcemap SourceMap
SourcesContent SourcesContent

Target Target
Format Format
GlobalName string
Expand Down
58 changes: 30 additions & 28 deletions pkg/api/api_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -521,34 +521,35 @@ func rebuildImpl(
Factory: validateJSX(log, buildOpts.JSXFactory, "factory"),
Fragment: validateJSX(log, buildOpts.JSXFragment, "fragment"),
},
Defines: validateDefines(log, buildOpts.Define, buildOpts.Pure),
Platform: validatePlatform(buildOpts.Platform),
SourceMap: validateSourceMap(buildOpts.Sourcemap),
MangleSyntax: buildOpts.MinifySyntax,
RemoveWhitespace: buildOpts.MinifyWhitespace,
MinifyIdentifiers: buildOpts.MinifyIdentifiers,
ASCIIOnly: validateASCIIOnly(buildOpts.Charset),
IgnoreDCEAnnotations: validateIgnoreDCEAnnotations(buildOpts.TreeShaking),
GlobalName: validateGlobalName(log, buildOpts.GlobalName),
CodeSplitting: buildOpts.Splitting,
OutputFormat: validateFormat(buildOpts.Format),
AbsOutputFile: validatePath(log, realFS, buildOpts.Outfile),
AbsOutputDir: validatePath(log, realFS, buildOpts.Outdir),
AbsOutputBase: validatePath(log, realFS, buildOpts.Outbase),
AbsMetadataFile: validatePath(log, realFS, buildOpts.Metafile),
OutputExtensionJS: outJS,
OutputExtensionCSS: outCSS,
ExtensionToLoader: validateLoaders(log, buildOpts.Loader),
ExtensionOrder: validateResolveExtensions(log, buildOpts.ResolveExtensions),
ExternalModules: validateExternals(log, realFS, buildOpts.External),
TsConfigOverride: validatePath(log, realFS, buildOpts.Tsconfig),
MainFields: buildOpts.MainFields,
PublicPath: buildOpts.PublicPath,
KeepNames: buildOpts.KeepNames,
InjectAbsPaths: make([]string, len(buildOpts.Inject)),
Banner: buildOpts.Banner,
Footer: buildOpts.Footer,
Plugins: plugins,
Defines: validateDefines(log, buildOpts.Define, buildOpts.Pure),
Platform: validatePlatform(buildOpts.Platform),
SourceMap: validateSourceMap(buildOpts.Sourcemap),
ExcludeSourcesContent: buildOpts.SourcesContent == SourcesContentExclude,
MangleSyntax: buildOpts.MinifySyntax,
RemoveWhitespace: buildOpts.MinifyWhitespace,
MinifyIdentifiers: buildOpts.MinifyIdentifiers,
ASCIIOnly: validateASCIIOnly(buildOpts.Charset),
IgnoreDCEAnnotations: validateIgnoreDCEAnnotations(buildOpts.TreeShaking),
GlobalName: validateGlobalName(log, buildOpts.GlobalName),
CodeSplitting: buildOpts.Splitting,
OutputFormat: validateFormat(buildOpts.Format),
AbsOutputFile: validatePath(log, realFS, buildOpts.Outfile),
AbsOutputDir: validatePath(log, realFS, buildOpts.Outdir),
AbsOutputBase: validatePath(log, realFS, buildOpts.Outbase),
AbsMetadataFile: validatePath(log, realFS, buildOpts.Metafile),
OutputExtensionJS: outJS,
OutputExtensionCSS: outCSS,
ExtensionToLoader: validateLoaders(log, buildOpts.Loader),
ExtensionOrder: validateResolveExtensions(log, buildOpts.ResolveExtensions),
ExternalModules: validateExternals(log, realFS, buildOpts.External),
TsConfigOverride: validatePath(log, realFS, buildOpts.Tsconfig),
MainFields: buildOpts.MainFields,
PublicPath: buildOpts.PublicPath,
KeepNames: buildOpts.KeepNames,
InjectAbsPaths: make([]string, len(buildOpts.Inject)),
Banner: buildOpts.Banner,
Footer: buildOpts.Footer,
Plugins: plugins,
}
for i, path := range buildOpts.Inject {
options.InjectAbsPaths[i] = validatePath(log, realFS, path)
Expand Down Expand Up @@ -771,6 +772,7 @@ func transformImpl(input string, transformOpts TransformOptions) TransformResult
JSX: jsx,
Defines: validateDefines(log, transformOpts.Define, transformOpts.Pure),
SourceMap: validateSourceMap(transformOpts.Sourcemap),
ExcludeSourcesContent: transformOpts.SourcesContent == SourcesContentExclude,
OutputFormat: validateFormat(transformOpts.Format),
GlobalName: validateGlobalName(log, transformOpts.GlobalName),
MangleSyntax: transformOpts.MinifySyntax,
Expand Down
17 changes: 17 additions & 0 deletions pkg/cli/cli_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,23 @@ func parseOptionsImpl(osArgs []string, buildOpts *api.BuildOptions, transformOpt
}
hasBareSourceMapFlag = false

case strings.HasPrefix(arg, "--sources-content="):
value := arg[len("--sources-content="):]
var sourcesContent api.SourcesContent
switch value {
case "false":
sourcesContent = api.SourcesContentExclude
case "true":
sourcesContent = api.SourcesContentInclude
default:
return fmt.Errorf("Invalid sources content: %q (valid: false, true)", value)
}
if buildOpts != nil {
buildOpts.SourcesContent = sourcesContent
} else {
transformOpts.SourcesContent = sourcesContent
}

case strings.HasPrefix(arg, "--sourcefile="):
if buildOpts != nil {
if buildOpts.Stdin == nil {
Expand Down
51 changes: 48 additions & 3 deletions scripts/js-api-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ let buildTests = {
async sourceMap({ esbuild, testDir }) {
const input = path.join(testDir, 'in.js')
const output = path.join(testDir, 'out.js')
await writeFileAsync(input, 'exports.foo = 123')
const content = 'exports.foo = 123'
await writeFileAsync(input, content)
await esbuild.build({ entryPoints: [input], outfile: output, sourcemap: true })
const result = require(output)
assert.strictEqual(result.foo, 123)
Expand All @@ -78,12 +79,15 @@ let buildTests = {
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.sourcesContent[0], content)
},

async sourceMapExternal({ esbuild, testDir }) {
const input = path.join(testDir, 'in.js')
const output = path.join(testDir, 'out.js')
await writeFileAsync(input, 'exports.foo = 123')
const content = 'exports.foo = 123'
await writeFileAsync(input, content)
await esbuild.build({ entryPoints: [input], outfile: output, sourcemap: 'external' })
const result = require(output)
assert.strictEqual(result.foo, 123)
Expand All @@ -93,19 +97,60 @@ let buildTests = {
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.sourcesContent[0], content)
},

async sourceMapInline({ esbuild, testDir }) {
const input = path.join(testDir, 'in.js')
const output = path.join(testDir, 'out.js')
await writeFileAsync(input, 'exports.foo = 123')
const content = 'exports.foo = 123'
await writeFileAsync(input, content)
await esbuild.build({ entryPoints: [input], outfile: output, sourcemap: 'inline' })
const result = require(output)
assert.strictEqual(result.foo, 123)
const outputFile = await readFileAsync(output, 'utf8')
const match = /\/\/# sourceMappingURL=data:application\/json;base64,(.*)/.exec(outputFile)
const json = JSON.parse(Buffer.from(match[1], 'base64').toString())
assert.strictEqual(json.version, 3)
assert.strictEqual(json.sources[0], path.basename(input))
assert.strictEqual(json.sourcesContent[0], content)
},

async sourceMapIncludeSourcesContent({ 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, sourcesContent: true })
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.sourcesContent[0], content)
},

async sourceMapExcludeSourcesContent({ 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, sourcesContent: false })
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.sourcesContent, void 0)
},

async resolveExtensionOrder({ esbuild, testDir }) {
Expand Down

0 comments on commit 442f2a7

Please sign in to comment.