Unused classes in export remain in bundle even after tree-shaking

I’m submitting a bug report

Webpack version:
2.1.0-beta.21

Please tell us about your environment:
Linux

Current behavior:

I have a class named Car in the file car.ts and two other classes for the engines in another file engine.ts.
car.ts

import { V8Engine, Engine } from './engine';

class SportsCar {
  constructor(private engine: Engine) {}

  toString() {
    return this.engine.toString() + ' Sports Car';
  }
}

engine.ts

export interface Engine {
  toString(): string;
}

export class V6Engine implements Engine {
  toString() {
    return 'V6';
  }
}

export class V8Engine implements Engine {
  toString() {
    return 'V8';
  }
}

export function getVersion() {
  return '1.0';
}

console.log(new SportsCar(new V8Engine()).toString());

The car.ts file only imports the V8Engine class, but the V6Engine class also appears even in the minified file. The unused function i export from engine.ts is stripped.

When running Webpack without UglifyJS plugin, the V6Engine class is marked with /* unused harmony export V6Engine */ as expected.
But after adding the plugin i get this warning message from UglifyJS:

    WARNING in car.prod.bundle.js from UglifyJs
    Dropping unused function getVersion [car.prod.bundle.js:89,9]
    Side effects in initialization of unused variable V6Engine [car.prod.bundle.js:73,132]

I have created a repository where the problem can be reproduced.
Cloning, installing and running npm run webpack and npm run webpack-prod reproduces the issue.

Don’t know if this is a bug with the Typescript transpilation, UglifyJS or the orchestration of these tools.
I’m using Typescript with es2015 modules and Webpack 2.

Expected/desired behavior:

The unused class should be removed in the minified file.

  • What is the motivation / use case for changing the behavior?
  • Browser: all
  • Language: TypeScript 2.0-dev

Author: Fantashit

6 thoughts on “Unused classes in export remain in bundle even after tree-shaking

  1. Another possible complication is with static class fields that are initialized with a function call.

    class Foo {
      static bar = (() => { console.log('side-effect :(') })()
    }

    Removing the declaration of Foo will also remove the side-effect of the declaration.

    Currently, this is transpiled to defining a property in the constructor, so even with “perfect ES6 support” removing the class, the IIFE still runs, but this (and class decorators) might become a problem in the future…

  2. We have a new kid on the block: Babili, it can strip unused classes after bundling.
    See example here.
    For now i only tried it with ES6 code, but it works. Only drawback it doesnt strip comments, but there is a ticket for that.
    babel/minify#67

    We might just need a Webpack plugin for Babili.

  3. Regarding unused classes: Transpiles (typescript, babel) need to annotate these transpiled class and minimizers need to handle this. webpack isn’t involved in this process (we already remove our reference to the export).

    This will may require unused package, I think we should annotate #PURE to webpack_require in variable declarator

    The spec says side effects in this package need to be evaluated. Annotating it with PURE would be incorrect according to the spec. But webpack 4 allows to opt-in into more aggressive optimization, which will do that. It will remove the module from the bundle.

  4. TypeScript 2.5 and later emits an @class comment on classes to inform minifiers that there are transpiled from an ES6 class. See microsoft/TypeScript#13721 for more details.
    It is also worth nothing that to use this with uglify a string-replace plugin is needed to convert to /** @class */ to /*@__PURE__*/. see mishoo/UglifyJS#2279 for more details.

Comments are closed.