Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tree-shaking seems to not work with Re-exporting / Aggregating #613

Closed
okikio opened this issue Dec 20, 2020 · 6 comments
Closed

Tree-shaking seems to not work with Re-exporting / Aggregating #613

okikio opened this issue Dec 20, 2020 · 6 comments

Comments

@okikio
Copy link

okikio commented Dec 20, 2020

Esbuild is awesome but I have found that Tree-shaking seems to not work with Re-exporting / Aggregating.

What I mean is that given an example like this:

// File - export.ts
export * from "./config";
export * from "./manager";
export * from "./url";

export * from "./history";
export * from "./page";

export * from "./emitter";
export * from "./service";

export * from "./transition";

export * from "./app";
export * from "./pjax";
export * from "./route";

export * from "@okikio/animate";

// File - import.ts
import { App } from "export.ts";

new App();

// File - tsconfig.json
{
    "compilerOptions": {
        "moduleResolution": "node",
        "target": "ES2020",
        "module": "ES2020",
        "sourceMap": true,
        "outDir": "lib",
        "declaration": true,
        "incremental": true,
        "isolatedModules": true,
        "esModuleInterop": true
    },
    "exclude": [
        "node_modules",
        "**/tests/**/*"
    ]
}

// File - build.js
esbuild({
    entryPoints: [`import.ts`],
    color: true,
    bundle: true,
    minify: true,
    sourcemap: true,
    outfile: "main.js",
    globalName,: "Export",
    tsconfig: "./tsconfig.json",
    logLevel: "info",
    format: "esm",
    target: ["chrome71"],
});

The bundle will result in all modules being bundled together, even though only one of the modules is in use.

@evanw
Copy link
Owner

evanw commented Dec 20, 2020

What do the files contain? If the files have side effects, they have to be bundled even if they aren't used since removing code with side effects would be incorrect.

@okikio
Copy link
Author

okikio commented Dec 20, 2020

Oh wow that was fast. The repo i am talking about is here https://github.com/okikio/native/blob/master/packages/native/src/api.ts

The surrounding files and folders should give a good understanding of the situation, however, I don't think the cause are side effects.

@okikio
Copy link
Author

okikio commented Dec 20, 2020

I did some more testing and if I directly import from the file I need without any re-exports, the bundle is much smaller, so, I conclude the problem isn't from side-effects, it's that esbuild can't treeshake re-exported files well. I use re-exporting because it's a neat and easy way to manage multiple files, so, it would be cool if esbuild could better support it.

If you need me to create a demo, or something else I would be happy to help.

@evanw
Copy link
Owner

evanw commented Dec 20, 2020

The repo was helpful. It looks like this is due to the top-level use of imported symbols:

import { Service } from "./service";
export class Router extends Service {
  ...
}

In this case Service is an imported symbol. The side-effect information is filled in by esbuild's parser. At this point esbuild doesn't know what type of file ./service is. It could hypothetically be a CommonJS module with a getter called Service that has side effects when called, so it's technically not safe to consider it side-effect free at that point. And there isn't enough information that survives past the parsing stage to be able to resolve this after that point when it's known that ./service is not a CommonJS module.

This is obviously a pretty extreme edge case. I could try to solve this such that it only works for non-CommonJS files which would preserve the getter with side-effect scenario. But arguably doing so would almost never be helpful and would likely still violate user expectations the vast majority of the time. So perhaps I should just consider all imported symbols to be side-effect free even if you could technically make one that isn't, since it's likely that no real-world code does that and it would prohibit tree shaking for side-effect free CommonJS modules.

I'll think more about what to do here.

@okikio
Copy link
Author

okikio commented Dec 20, 2020

Well if the problem is that esbuild doesn't know the type of file it should expect, why not use tsconfig to let it know that this is a module, as I already specified isolatedModules in the tsconfig so it is safe to assume that its a module with no side effects.

@evanw evanw closed this as completed in d5f7fdd Dec 21, 2020
@okikio
Copy link
Author

okikio commented Dec 21, 2020

Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants