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

Treeshaking with compile-time constants #17898

Open
4 tasks done
LorisSigrist opened this issue Aug 19, 2024 · 3 comments
Open
4 tasks done

Treeshaking with compile-time constants #17898

LorisSigrist opened this issue Aug 19, 2024 · 3 comments
Labels
has workaround p2-edge-case Bug, but has workaround or limited in scope (priority)

Comments

@LorisSigrist
Copy link

Description

I've been trying to get treeshaking to work with compile-time computed keys. Consider the following:

// src/index.js
import * as m from "./my-module.js"
console.log(m['hello_' + __defined_value__])

// vite.config.js
const config = {
  define: {
    __defined_value__: JSON.stringify("world")
  }
}

In this example, the call to m['hello_' + __defined_value__] could (should) be reduced to m['hello_world'] before treeshaking. That way only the hello_world export from ./my-module.js is used and everything else is treeshaken.

Currently all exports from ./my-module.js would be included in the build-output.

Suggested solution

AFAIK Rollup currently doesn't do this kind of transform, however, EsBuild does during it's minification step. It would output the following code

import * as m from "./my-module.js"

const thing = "world"; 
m["hello_world"] // the string get's computed and inlined

The issue is timing.

This transform would need to happen after the vite:define plugin, but before the build plugins. Currently EsBuild's minification only runs after the build has already completed.

This results in a weird output where the key is pre-computed in the output, but no treeshaking has taken place:

// dist/assets/index-[hash].js
const e = Object.freeze(
  Object.defineProperty(
    {
      __proto__: null,
      hello_world: "Hello World",
      extra: "I should have been treeshaken",
    },
    Symbol.toStringTag,
    { value: "Module" }
  )
);
console.log(e.hello_world()); // correctly computed & inlined

Vite could achieve the desired optimization by running esbuild on *.{js|ts|jsx|tsx} files before running rollup. This wouldn't need to be full-blown minification, just the constant-folding step.

Alternatively this optimization could be added to rollup itself.

Alternative

Users could manually provide a plugin that runs EsBuild minification after Vite's built-in plugins but before the build plugins. I believe enforce: undefined gives that behavior.

Additional context

I work on the Paraglide i18n library, which could benefit greatly from this. See: opral/inlang-paraglide-js#164

Validations

@sapphi-red
Copy link
Member

It worked with this example. Would you create a reproduction?
https://stackblitz.com/edit/vitejs-vite-rapg5b?file=vite.config.js,main.js&terminal=dev

@LorisSigrist
Copy link
Author

Of course, here is a minimal repo: https://github.com/LorisSigrist/vite-17898-reproduction

@sapphi-red
Copy link
Member

Ah, I found that the tree-shake works with console.log(m['new_year_' + __YEAR__]()) but not with console.log(m[`new_year_${__YEAR__}`]()).

It seems it's because esbuild replaces 'new_year_' + __YEAR__ with "new_year_2024" and 'new_year_' + __YEAR__ with new_year_${"2024"}. (When minify is not enabled)

@sapphi-red sapphi-red added has workaround p2-edge-case Bug, but has workaround or limited in scope (priority) labels Aug 19, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
has workaround p2-edge-case Bug, but has workaround or limited in scope (priority)
Projects
None yet
Development

No branches or pull requests

2 participants