Skip to content

esbuild Does not Always Inline IIFEs #4354

@dgp1130

Description

@dgp1130

It seems like esbuild generally inlines IIFEs, but at least sometimes this does not seem to be happening. One example:

// Input

function x(): string {
  console.log('x');
  return 'x';
}

function y(): void {
  const cb = () => {
    console.log(x());
  };

  return cb();
}

y();
// Current output

function x() {
  return console.log("x"), "x";
}
function y() {
  // Unnecessary `return` and IIFE
  return (() => {
    console.log(x());
  })();
}
y();
// Expected output

function x() {
  return console.log("x"), "x";
}
function y() {
  console.log(x());
}
y();

Playground link

Ideally, this should be able to further inline the only calls of x and y into:

// Ideal output

console.log(console.log("x"), "x");

This is may be related in terms of function call inline, but is potentially more involved than just inlining an IIFE.

I ran into this today when attempting to conditionally wrap a function based on a --define value like so:

declare global {
  var isDebug: boolean | undefined;
}

function doSomething(): void {
  const callback = () => {
    console.log('something');
  };

  if (isDebug) {
    callWithDebugInfo(callback);
  } else {
    callback();
  }
}

function callWithDebugInfo(callback: () => void): void {
  setDebugInfo({/* ... */});
  try {
    callback();
  } finally {
    removeDebugInfo({/* ... */});
  }
}

esbuild is able to correctly tree-shake callWithDebugInfo with --define:isDebug=false, but this leaves an extra IIFE in the production optimization (unable to properly inline callback()), which is a cost I'd prefer not to pay for this debug feature.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions