Skip to content

Commit

Permalink
make banner and footer also work for css
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Mar 7, 2021
1 parent 5b38556 commit ee678c6
Show file tree
Hide file tree
Showing 13 changed files with 143 additions and 48 deletions.
34 changes: 34 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,40 @@

If you are using esbuild in the browser, you now need to call `esbuild.initialize({ wasmURL })` and wait for the returned promise before calling `esbuild.transform()`. It takes the same options that `esbuild.startService()` used to take. Note that the `esbuild.buildSync()` and `esbuild.transformSync()` APIs still exist when using esbuild in node. Nothing has changed about the synchronous esbuild APIs.

* The banner and footer options are now language-specific ([#712](https://github.com/evanw/esbuild/issues/712))

The `--banner=` and `--footer=` options now require you to pass the file type:

* CLI:

```
esbuild --banner:js=//banner --footer:js=//footer
esbuild --banner:css=/*banner*/ --footer:css=/*footer*/
```

* JavaScript

```js
esbuild.build({
banner: { js: '//banner', css: '/*banner*/' },
footer: { js: '//footer', css: '/*footer*/' },
})
```

* Go

```go
api.Build(api.BuildOptions{
Banner: map[string]string{"js": "//banner"},
Footer: map[string]string{"js": "//footer"},
})
api.Build(api.BuildOptions{
Banner: map[string]string{"css": "/*banner*/"},
Footer: map[string]string{"css": "/*footer*/"},
})
```

This was changed because the feature was originally added in a JavaScript-specific manner, which was an oversight. CSS banners and footers must be separate from JavaScript banners and footers to avoid injecting JavaScript syntax into your CSS files.

## Unreleased

Expand Down
6 changes: 4 additions & 2 deletions cmd/esbuild/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,13 @@ var helpText = func(colors logger.Colors) string {
--watch Watch mode: rebuild on file system changes
` + colors.Bold + `Advanced options:` + colors.Default + `
--banner=... Text to be prepended to each output file
--banner:T=... Text to be prepended to each output file of type T
where T is one of: css | js
--charset=utf8 Do not escape UTF-8 code points
--color=... Force use of color terminal escapes (true | false)
--error-limit=... Maximum error count or 0 to disable (default 10)
--footer=... Text to be appended to each output file
--footer:T=... Text to be appended to each output file of type T
where T is one of: css | js
--global-name=... The name of the global for the IIFE format
--inject:F Import the file F into all input files and
automatically replace matching globals with imports
Expand Down
20 changes: 15 additions & 5 deletions internal/bundler/linker.go
Original file line number Diff line number Diff line change
Expand Up @@ -3750,10 +3750,10 @@ func (repr *chunkReprJS) generate(c *linkerContext, chunk *chunkInfo) func(gener
}
}

if len(c.options.Banner) > 0 {
prevOffset.advanceString(c.options.Banner)
if len(c.options.JSBanner) > 0 {
prevOffset.advanceString(c.options.JSBanner)
prevOffset.advanceString("\n")
j.AddString(c.options.Banner)
j.AddString(c.options.JSBanner)
j.AddString("\n")
}

Expand Down Expand Up @@ -3971,8 +3971,8 @@ func (repr *chunkReprJS) generate(c *linkerContext, chunk *chunkInfo) func(gener
j.AddString("\n")
}

if len(c.options.Footer) > 0 {
j.AddString(c.options.Footer)
if len(c.options.JSFooter) > 0 {
j.AddString(c.options.JSFooter)
j.AddString("\n")
}

Expand Down Expand Up @@ -4187,6 +4187,11 @@ func (repr *chunkReprCSS) generate(c *linkerContext, chunk *chunkInfo) func(gene
j := js_printer.Joiner{}
newlineBeforeComment := false

if len(c.options.CSSBanner) > 0 {
j.AddString(c.options.CSSBanner)
j.AddString("\n")
}

// Generate any prefix rules now
{
ast := css_ast.AST{}
Expand Down Expand Up @@ -4286,6 +4291,11 @@ func (repr *chunkReprCSS) generate(c *linkerContext, chunk *chunkInfo) func(gene
j.AddString("\n")
}

if len(c.options.CSSFooter) > 0 {
j.AddString(c.options.CSSFooter)
j.AddString("\n")
}

// The CSS contents are done now that the source map comment is in
cssContents := j.Done()

Expand Down
7 changes: 5 additions & 2 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,8 +217,11 @@ type Options struct {
InjectAbsPaths []string
InjectedDefines []InjectedDefine
InjectedFiles []InjectedFile
Banner string
Footer string

JSBanner string
JSFooter string
CSSBanner string
CSSFooter string

ChunkPathTemplate []PathTemplate
AssetPathTemplate []PathTemplate
Expand Down
23 changes: 18 additions & 5 deletions lib/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,6 @@ function pushCommonFlags(flags: string[], options: CommonOptions, keys: OptionKe
let define = getFlag(options, keys, 'define', mustBeObject);
let pure = getFlag(options, keys, 'pure', mustBeArray);
let keepNames = getFlag(options, keys, 'keepNames', mustBeBoolean);
let banner = getFlag(options, keys, 'banner', mustBeString);
let footer = getFlag(options, keys, 'footer', mustBeString);

if (sourcesContent !== void 0) flags.push(`--sources-content=${sourcesContent}`);
if (target) {
Expand All @@ -137,9 +135,6 @@ function pushCommonFlags(flags: string[], options: CommonOptions, keys: OptionKe
}
if (pure) for (let fn of pure) flags.push(`--pure:${fn}`);
if (keepNames) flags.push(`--keep-names`);

if (banner) flags.push(`--banner=${banner}`);
if (footer) flags.push(`--footer=${footer}`);
}

function flagsForBuildOptions(
Expand Down Expand Up @@ -188,6 +183,8 @@ function flagsForBuildOptions(
let chunkNames = getFlag(options, keys, 'chunkNames', mustBeString);
let assetNames = getFlag(options, keys, 'assetNames', mustBeString);
let inject = getFlag(options, keys, 'inject', mustBeArray);
let banner = getFlag(options, keys, 'banner', mustBeObject);
let footer = getFlag(options, keys, 'footer', mustBeObject);
let entryPoints = getFlag(options, keys, 'entryPoints', mustBeArray);
let absWorkingDir = getFlag(options, keys, 'absWorkingDir', mustBeString);
let stdin = getFlag(options, keys, 'stdin', mustBeObject);
Expand Down Expand Up @@ -239,6 +236,18 @@ function flagsForBuildOptions(
flags.push(`--main-fields=${values.join(',')}`);
}
if (external) for (let name of external) flags.push(`--external:${name}`);
if (banner) {
for (let type in banner) {
if (type.indexOf('=') >= 0) throw new Error(`Invalid banner file type: ${type}`);
flags.push(`--banner:${type}=${banner[type]}`);
}
}
if (footer) {
for (let type in footer) {
if (type.indexOf('=') >= 0) throw new Error(`Invalid footer file type: ${type}`);
flags.push(`--footer:${type}=${footer[type]}`);
}
}
if (inject) for (let path of inject) flags.push(`--inject:${path}`);
if (loader) {
for (let ext in loader) {
Expand Down Expand Up @@ -311,12 +320,16 @@ function flagsForTransformOptions(
let tsconfigRaw = getFlag(options, keys, 'tsconfigRaw', mustBeStringOrObject);
let sourcefile = getFlag(options, keys, 'sourcefile', mustBeString);
let loader = getFlag(options, keys, 'loader', mustBeString);
let banner = getFlag(options, keys, 'banner', mustBeString);
let footer = getFlag(options, keys, 'footer', mustBeString);
checkForInvalidFlags(options, keys, `in ${callName}() call`);

if (sourcemap) flags.push(`--sourcemap=${sourcemap === true ? 'external' : sourcemap}`);
if (tsconfigRaw) flags.push(`--tsconfig-raw=${typeof tsconfigRaw === 'string' ? tsconfigRaw : JSON.stringify(tsconfigRaw)}`);
if (sourcefile) flags.push(`--sourcefile=${sourcefile}`);
if (loader) flags.push(`--loader=${loader}`);
if (banner) flags.push(`--banner=${banner}`);
if (footer) flags.push(`--footer=${footer}`);

return flags;
}
Expand Down
6 changes: 4 additions & 2 deletions lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ interface CommonOptions {
define?: { [key: string]: string };
pure?: string[];
keepNames?: boolean;
banner?: string;
footer?: string;

color?: boolean;
logLevel?: LogLevel;
Expand All @@ -53,6 +51,8 @@ export interface BuildOptions extends CommonOptions {
chunkNames?: string;
assetNames?: string;
inject?: string[];
banner?: { [type: string]: string };
footer?: { [type: string]: string };
incremental?: boolean;
entryPoints?: string[];
stdin?: StdinOptions;
Expand Down Expand Up @@ -158,6 +158,8 @@ export interface TransformOptions extends CommonOptions {

sourcefile?: string;
loader?: Loader;
banner?: string;
footer?: string;
}

export interface TransformResult {
Expand Down
4 changes: 2 additions & 2 deletions pkg/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,8 +260,8 @@ type BuildOptions struct {
OutExtensions map[string]string
PublicPath string
Inject []string
Banner string
Footer string
Banner map[string]string
Footer map[string]string
NodePaths []string // The "NODE_PATH" variable from Node.js

ChunkNames string
Expand Down
31 changes: 27 additions & 4 deletions pkg/api/api_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,20 @@ func validateOutputExtensions(log logger.Log, outExtensions map[string]string) (
return
}

func validateBannerOrFooter(log logger.Log, name string, values map[string]string) (js string, css string) {
for key, value := range values {
switch key {
case "js":
js = value
case "css":
css = value
default:
log.AddError(nil, logger.Loc{}, fmt.Sprintf("Invalid %s file type: %q (valid: css, js)", name, key))
}
}
return
}

func convertLocationToPublic(loc *logger.MsgLocation) *Location {
if loc != nil {
return &Location{
Expand Down Expand Up @@ -685,6 +699,8 @@ func rebuildImpl(
}
jsFeatures, cssFeatures := validateFeatures(log, buildOpts.Target, buildOpts.Engines)
outJS, outCSS := validateOutputExtensions(log, buildOpts.OutExtensions)
bannerJS, bannerCSS := validateBannerOrFooter(log, "banner", buildOpts.Banner)
footerJS, footerCSS := validateBannerOrFooter(log, "footer", buildOpts.Footer)
defines, injectedDefines := validateDefines(log, buildOpts.Define, buildOpts.Pure)
options := config.Options{
UnsupportedJSFeatures: jsFeatures,
Expand Down Expand Up @@ -723,8 +739,10 @@ func rebuildImpl(
KeepNames: buildOpts.KeepNames,
InjectAbsPaths: make([]string, len(buildOpts.Inject)),
AbsNodePaths: make([]string, len(buildOpts.NodePaths)),
Banner: buildOpts.Banner,
Footer: buildOpts.Footer,
JSBanner: bannerJS,
JSFooter: footerJS,
CSSBanner: bannerCSS,
CSSFooter: footerCSS,
PreserveSymlinks: buildOpts.PreserveSymlinks,
WatchMode: buildOpts.Watch != nil,
Plugins: plugins,
Expand Down Expand Up @@ -1157,8 +1175,13 @@ func transformImpl(input string, transformOpts TransformOptions) TransformResult
Contents: input,
SourceFile: transformOpts.Sourcefile,
},
Banner: transformOpts.Banner,
Footer: transformOpts.Footer,
}
if options.Stdin.Loader == config.LoaderCSS {
options.CSSBanner = transformOpts.Banner
options.CSSFooter = transformOpts.Footer
} else {
options.JSBanner = transformOpts.Banner
options.JSFooter = transformOpts.Footer
}
if options.SourceMap == config.SourceMapLinkedWithComment {
// Linked source maps don't make sense because there's no output file name
Expand Down
32 changes: 20 additions & 12 deletions pkg/cli/cli_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ func newBuildOptions() api.BuildOptions {
return api.BuildOptions{
Loader: make(map[string]api.Loader),
Define: make(map[string]string),
Banner: make(map[string]string),
Footer: make(map[string]string),
}
}

Expand Down Expand Up @@ -347,21 +349,27 @@ func parseOptionsImpl(osArgs []string, buildOpts *api.BuildOptions, transformOpt
transformOpts.JSXFragment = value
}

case strings.HasPrefix(arg, "--banner="):
value := arg[len("--banner="):]
if buildOpts != nil {
buildOpts.Banner = value
} else {
transformOpts.Banner = value
case strings.HasPrefix(arg, "--banner=") && transformOpts != nil:
transformOpts.Banner = arg[len("--banner="):]

case strings.HasPrefix(arg, "--footer=") && transformOpts != nil:
transformOpts.Footer = arg[len("--footer="):]

case strings.HasPrefix(arg, "--banner:") && buildOpts != nil:
value := arg[len("--banner:"):]
equals := strings.IndexByte(value, '=')
if equals == -1 {
return fmt.Errorf("Missing \"=\": %q", value)
}
buildOpts.Banner[value[:equals]] = value[equals+1:]

case strings.HasPrefix(arg, "--footer="):
value := arg[len("--footer="):]
if buildOpts != nil {
buildOpts.Footer = value
} else {
transformOpts.Footer = value
case strings.HasPrefix(arg, "--footer:") && buildOpts != nil:
value := arg[len("--footer:"):]
equals := strings.IndexByte(value, '=')
if equals == -1 {
return fmt.Errorf("Missing \"=\": %q", value)
}
buildOpts.Footer[value[:equals]] = value[equals+1:]

case strings.HasPrefix(arg, "--error-limit="):
value := arg[len("--error-limit="):]
Expand Down
6 changes: 3 additions & 3 deletions scripts/end-to-end-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -2763,13 +2763,13 @@

// Test injecting banner and footer
tests.push(
test(['in.js', '--outfile=node.js', '--banner=const bannerDefined = true;'], {
test(['in.js', '--outfile=node.js', '--banner:js=const bannerDefined = true;'], {
'in.js': `if (!bannerDefined) throw 'fail'`
}),
test(['in.js', '--outfile=node.js', '--footer=function footer() { }'], {
test(['in.js', '--outfile=node.js', '--footer:js=function footer() { }'], {
'in.js': `footer()`
}),
test(['a.js', 'b.js', '--outdir=out', '--bundle', '--format=cjs', '--banner=const bannerDefined = true;', '--footer=function footer() { }'], {
test(['a.js', 'b.js', '--outdir=out', '--bundle', '--format=cjs', '--banner:js=const bannerDefined = true;', '--footer:js=function footer() { }'], {
'a.js': `
module.exports = { banner: bannerDefined, footer };
`,
Expand Down
4 changes: 2 additions & 2 deletions scripts/esbuild.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,8 @@ exports.buildWasmLib = async (esbuildPath) => {
'--format=cjs',
'--define:ESBUILD_VERSION=' + JSON.stringify(version),
'--define:WEB_WORKER_SOURCE_CODE=' + JSON.stringify(wasmWorkerCode.umd),
'--banner=' + umdPrefix,
'--footer=' + umdSuffix,
'--banner:js=' + umdPrefix,
'--footer:js=' + umdSuffix,
'--log-level=warning',
].concat(minifyFlags), { cwd: repoDir }).toString()
fs.writeFileSync(path.join(libDir, minify ? 'browser.min.js' : 'browser.js'), browserCJS)
Expand Down
Loading

0 comments on commit ee678c6

Please sign in to comment.