evanw / esbuild

An extremely fast bundler for the web
https://esbuild.github.io/
MIT License
37.93k stars 1.13k forks source link

Empty common chunks (after tree-shaking) are not removed. #1747

Open hyrious opened 2 years ago

hyrious commented 2 years ago

Problem: Using require with splitting enabled generates an extra chunk of __require as an dependency of all output files.

Or: Empty common chunks are not collapsed.

What's that mean? see reproduction below (or see it live):

Reproduce:

// a.js
require; // simplified `var __require = ...`,
         // let's just keep the side effects.
console.log('a')

// b.js
console.log('b')
$ esbuild a.js b.js --bundle --format=esm --splitting --outdir=dist
// dist/a.js
import "./chunk-BYXBJQAS.js";

// a.js
console.log("a");
// dist/b.js
import "./chunk-BYXBJQAS.js"; // <- this line shouldn't exist
                              // as b.js does not depend on `require`
// b.js
console.log("b");
// dist/chunk-BYXBJQAS.js
(!!! EMPTY !!!)
// this file actually has an `__require` definition,
// but is tree-shaked after bundling
// dist/chunk-BYXBJQAS.js, with --tree-shaking=false
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
  get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
}) : x)(function(x) {
  if (typeof require !== "undefined")
    return require.apply(this, arguments);
  throw new Error('Dynamic require of "' + x + '" is not supported');
});

export {
  __require
};
hyrious commented 2 years ago

Looks like it has been fixed in 0.14.5?

Update: maybe not, I'll have to test more to come up with a newer reproduction.

hyrious commented 2 years ago

Now to reproduce it, you have to having require in 2+ files with splitting on: (live link).

I guess this is because shared runtime helpers are merged before being shaked. As we know esbuild works in this order:

  1. read and apply constant folding to each file
  2. link them, apply tree shaking (we miss some DCE here)

In other words, any tree-shakeable common trunks can leave an empty output -- (see it live)

// a.js (entry point)
import { x } from "./x.js"
// b.js (entry point)
import { x } from "./x.js"
// x.js
export var x = 42

// a.js
import "./chunk-BYXBJQAS.js";
// b.js
import "./chunk-BYXBJQAS.js";
// chunk-BYXBJQAS.js
(empty)
tennox commented 8 months ago

Encountered this today, still reproducible: https://hyrious.me/esbuild-repl/?version=0.14.23&b=e%00a.js%00import+%7B+x+%7D+from+%22.%2Fx.js%22&b=e%00b.js%00import+%7B+x+%7D+from+%22.%2Fx.js%22&b=%00x.js%00export+var+x+%3D+1&o=%7B%0A++%22bundle%22%3A+true%2C%0A++%22format%22%3A+%22esm%22%2C%0A++%22splitting%22%3A+true%2C%0A++%22treeShaking%22%3A+false%2C%0A++%22outdir%22%3A+%22%2F%22%2C%0A++%22allowOverwrite%22%3A+true%0A%7D

hyrious commented 8 months ago

@tennox Your link is still using 0.14.23 version of esbuild (you can change the version at the top-right corner). Here's the latest reproduction: https://esbuild.github.io/try/#YgAwLjE5LjEwAHsKICAiYnVuZGxlIjogdHJ1ZSwKICAiZm9ybWF0IjogImVzbSIsCiAgInNwbGl0dGluZyI6IHRydWUsCiAgInRyZWVTaGFraW5nIjogZmFsc2UsCiAgIm91dGRpciI6ICIvIiwKICAiYWxsb3dPdmVyd3JpdGUiOiB0cnVlCn0AZQBhLmpzAGltcG9ydCB7IHggfSBmcm9tICIuL3guanMiAGUAYi5qcwBpbXBvcnQgeyB4IH0gZnJvbSAiLi94LmpzIgAAeC5qcwBleHBvcnQgdmFyIHggPSAx