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

Pure annotation for functions #1883

Closed
skyrpex opened this issue Dec 23, 2021 · 10 comments
Closed

Pure annotation for functions #1883

skyrpex opened this issue Dec 23, 2021 · 10 comments

Comments

@skyrpex
Copy link

skyrpex commented Dec 23, 2021

Currently, the /* @__PURE__ */ annotation must be used individually on every returned value for a given call.

Could it be possible to allow using the annotation on a function, so all its returned values are considered pure? Like in the following.

/**
 * The function is pure, so if the result is not used, it can be removed.
 * 
 * @__PURE__
 */
function addTwo(number: number) {
    return number + 2;
}

// Will be removed
addTwo(4);

// Will be removed
const x = addTwo(3);

// This will be kept
export const y = addTwo(2);
@skyrpex skyrpex changed the title Pure annotation for function return values Pure annotation for functions Dec 23, 2021
@evanw
Copy link
Owner

evanw commented Dec 23, 2021

I understand why you might want it to work this way, but esbuild's implementation is the way __PURE__ behaves in other tools too, and I think there's value in having this annotation work the same way in all tools instead of having it behave differently depending on the tool. That way people will author packages in a way that works well across tools. There is no formal specification but I believe this convention was originally developed by UglifyJS: mishoo/UglifyJS#1448. So it should work however they have made it work.

I'm mainly building esbuild to follow existing conventions in the community regarding this stuff. I don't want to have esbuild start inventing new connections that will fragment the community. If there is community momentum around a new convention, then it might make sense to add it to esbuild, but I don't think esbuild is the right tool to be innovating on this kind of thing. Other tools are much more widely used, so it makes sense for these discussions to start with those tools first instead of esbuild in my opinion.

@skyrpex
Copy link
Author

skyrpex commented Dec 23, 2021 via email

@evanw
Copy link
Owner

evanw commented Dec 24, 2021

I don't think it needs to be super formal. From what I understand, UglifyJS and Terser are the JS minifiers that are used most often. UglifyJS has 10x more downloads than esbuild for example: https://www.npmtrends.com/esbuild-vs-terser-vs-uglify-js. So you could just start there and see if there's any interest. There may also be an existing discussion about this somewhere since there are surely others who want this feature too. If such a discussion exists, it would also be helpful to read for context.

@evanw
Copy link
Owner

evanw commented Jan 6, 2022

I'm going to close this since I'm not planning to work on this. If something like this ships in other more widely-used tools and becomes adopted by the community (i.e. is used in a significant number of popular packages), then we can reopen this issue or create a new one.

@evanw evanw closed this as completed Jan 6, 2022
@bigbanana
Copy link

bigbanana commented Feb 18, 2022

Sadly, I want the same functionality, but I found there are other ways to do it.
like this

function _addTwo(number: number) {
    return number + 2;
}

function addTwo(number: number) {
    const r = /* #__PURE__ */ _addTwo(number);
    return r;
}

// Will be removed
addTwo(4);

// Will be removed
const x = addTwo(3);

// This will be kept
export const y = addTwo(2);

@raveclassic
Copy link

@bigbanana did you try it? I can't make it work with the latest [email protected].

@evanw I think my case is quite widespread - I'm using https://github.com/colinhacks/zod to build schema validators. Essentially, the library is a bunch of functions which one can use to build module-level constants holding the validators which then can be used on demand.

The issue is that without this /* #__PURE__ */ annotations, if I have a shared library with common validators, all of them are bundled, even those that are not used directly in the code. It's quite tedious to add these comments to each constant and very hard to maintain. It would be really really nice if esbuild supported such annotations on function declarations, maybe at least via a plugin.

Right now I have to add an extra transpilation step with a custom babel plugin that just adds these annotations before each call to zod builder. This makes the bundling slow as hell and ultimately defeats the purpose of having super fast build with esbuild.

Looking forward to continuing this discussion as it seems the demand is quite high due to how such case is widespread. Essentially, this is a case for any "builder" pattern, whether it's zod, io-ts, injectable-ts, HOF, React HOCs, you name it. It would be extremely handy to have a way to declare function itself as pure. Maybe with annotating function body?
Example:

function buildString() /* #__PURE__ */ {
  // function body
}

// removed by tree-shaking
const fooString = buildString()
// kept by tree-shaking
export const barString = buildString()

@mattfysh
Copy link

I suspect many have this issue, as you will tend to declare zod schemas upfront at the top of your module, much like you would a typescript type.

I'm also generating a lot of my zod code using ts-to-zod, so I'll need to try and find a way to hook into compiler and add the required annotations. It would be nice though if esbuild could carve out a solution for zod + tree shaking, especially in cases where tree shaking is a priority

@skyrpex
Copy link
Author

skyrpex commented Jun 2, 2023

Thanks to this tweet it has come to my attention Google Closure Compiler's @nosideeffects annotation.

From their docs:

@nosideeffects indicates that a call to the declared function has no side effects. This annotation allows the compiler to remove calls to the function if the return value is not used. This is not a signal that the function is "pure": it may still read mutable global state.

Much better than the hypotetical "pure" keyword for functions.

@aleclarson
Copy link

Rollup has support for this with #__NO_SIDE_EFFECTS__ annotation.

rollup/rollup#5024

@evanw
Copy link
Owner

evanw commented Jul 18, 2023

Yes. See also #3149, which is already linked above.

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

6 participants