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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dead-code elimination of unused default parameters #7961

Closed
bitjson opened this issue Apr 15, 2022 · 2 comments
Closed

Dead-code elimination of unused default parameters #7961

bitjson opened this issue Apr 15, 2022 · 2 comments

Comments

@bitjson
Copy link

bitjson commented Apr 15, 2022

馃檵 Feature Request

During dead-code elimination/tree shaking, default parameters that aren't used by the bundle can be removed.

馃 Expected Behavior

For this simple example:

module.js:

const internalAdd = (a, b) => (a === '0' ? b : a + b); // should not appear in bundle
export const addAndLog = (a, b, impl = internalAdd) => console.log(impl(a, b));

app.js:

import { addAndLog } from './module.js';
const myAdd = (a, b) => a + b;
addAndLog(1, 2, myAdd); // => 3

The produced bundle should include only:

const addAndLog = (a, b, impl) => console.log(impl(a, b));
const myAdd = (a, b) => a + b;
addAndLog(1, 2, myAdd); // => 3

馃槸 Current Behavior

Currently, parcel bundles the dead code:

const internalAdd = (a, b) => (a === '0' ? b : a + b); // should not appear in bundle
const addAndLog = (a, b, impl = internalAdd) => console.log(impl(a, b));
const myAdd = (a, b) => a + b;
addAndLog(1, 2, myAdd); // => 3

馃敠 Context

I maintain Libauth, a library that offers WebAssembly crypto implementations (ripemd160, sha1, sha256, sha512, and secp256k1). As a pure ESM library, Libauth can asynchronously instantiate each WASM implementation internally, exporting simple interfaces that behave like collections of JS-only functions (with better performance). Many of Libauth's exported functions also use one of these built-in WASM instances as a default parameter. For example, decodeBase58Address has the definition:

export const decodeBase58Address = (
  address: string,
  sha256: { hash: (input: Uint8Array) => Uint8Array } = internalSha256
) => {
  // ...
};

Most applications can call decodeBase58Address(address) to automatically use the default, WASM-based sha256 implementation (internalSha256).

However, applications that already have another sha256 implementation can provide that implementation as the second parameter: decodeBase58Address(address, mySha256Implementation). In this case, the default parameter (internalSha256) is dead code, and should be possible to eliminate from the application's bundle, saving hundreds of KBs from those bundles.

馃捇 Examples

A detailed example (testing with all known bundlers) is available at this repo: https://github.com/bitjson/shake-default-params

@mischnic
Copy link
Member

mischnic commented Apr 16, 2022

I agree with evanw/esbuild#2185 (comment). The only DCE done by Parcel itself is excluding unused files (if they have sideEffects: false). Apart from that it just tries to transform as many imports into regular top-level functions calls as possible (and not with a namespace object as i.e. Babel does _module.addAndLog()). The actual dead code is then removed by Terser/whatever minified you want to use afterwards.

Right now, Terser turns

const internalAdd = (a, b) => "dead";
const addAndLog = (impl = internalAdd) => console.log(impl(1, 2));
addAndLog(3)

into

const o=(o,c)=>"dead";((c=o)=>{console.log(c(1,2))})(3);

@bitjson
Copy link
Author

bitjson commented May 17, 2022

Thanks for the info @mischnic! I just opened an issue for terser: terser/terser#1199

For anyone finding this issue before Parcel/terser support this optimization, Rollup will support tree-shaking default parameters; the PR is here: rollup/rollup#4498).

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

No branches or pull requests

2 participants