Skip to content

Effectful imports should ignore sideEffects field #6571

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

Closed
Andarist opened this issue Feb 25, 2018 · 48 comments
Closed

Effectful imports should ignore sideEffects field #6571

Andarist opened this issue Feb 25, 2018 · 48 comments

Comments

@Andarist
Copy link
Contributor

Andarist commented Feb 25, 2018

Do you want to request a feature or report a bug?

feature

What is the current behavior?

import 'list/fantasy-land'

with

sideEffects: false

does not include imported file in the bundle, which is somewhat OK

If the current behavior is a bug, please provide the steps to reproduce.

not a bug

What is the expected behavior?

this import is clearly effectful, so it actually IMHO should not get dropped regardless of the sideEffects

If this is a feature request, what is motivation or use case for changing the behavior?

described in expected behaviour

Please mention other relevant information such as the browser version, Node.js version, webpack version, and Operating System.

webpack@4, others - irrelevant

@sokra
Copy link
Member

sokra commented Feb 26, 2018

If import 'list/fantasy-land' has a side-effect, you sideEffects configuration is wrong.

You can and should include list/fantasy-land in your configuration like this:

// package.json of 'list'
{
  "sideEffects": [
    "./fantasy-land"
  ]
}

@Andarist
Copy link
Contributor Author

I understand how to solve it, I've did it just before posting an issue here and I understand this is not the most popular case, but still think that my argument has some merit - import 'package' is side-effectful "by definition" and sideEffects could skip such imports by default.

@quantizor
Copy link
Contributor

I agree with @Andarist. If you consider the import statement, a naked import implies “execute everything in this file”, which is side effectful by default.

@dcporter
Copy link

dcporter commented Apr 24, 2018

I also agree with @Andarist. MDN describes import '/modules/my-module.js'; as "Import a module for its side effects only", and such imports have no other purpose. The use of this form of import syntax is sufficient information to indicate that the imported module has side effects.

So the developer has already specified that this module has side effects; the requirement to specify it again in a separate location is suboptimal and fragile.

@rtsao
Copy link
Contributor

rtsao commented Jun 12, 2018

In practice, it might be nice to configure webpack to assume all modules are side effect free by default, as the whole npm ecosystem doesn't use it yet.

If webpack treated an import statement with no specifiers (i.e. import "foo") as a signal for side effects, this would be a convenient, zero-config way to opt out of this behavior (as opposed to #6074).

For example:

import {something} from "foo";

// unused import gets pruned
import "foo";
// implies side effects

import {something} from "foo";

// unused import does not get pruned

This behavior could be nice combination with:

  • Warnings when imports are pruned but not explicitly "sideEffects": false
  • Warnings when imports are explicitly "sideEffects": false but they have been imported without specifiers (i.e. import "foo";)

@edmorley
Copy link
Contributor

edmorley commented Sep 28, 2018

Hi

In facebook/create-react-app#5140 the package in question mistakenly set "sideEffects": false even though it includes CSS, which caused the CSS to be dropped when mode was 'production'.

This lead to the discussion here:
facebook/create-react-app#5141 (comment)

And similar confusion occurred in #6741 and #7791.

@sokra, what do you think about the idea of allowing loaders to declare that their output always has side effects, thereby overriding the package.json sideEffects (perhaps emitting a warning whilst doing so)? css-loader could then use this to declare that its output should never be optimized away by SideEffectsFlagPlugin.

@gaearon
Copy link
Contributor

gaearon commented Sep 28, 2018

I disagree that the import form should determine dead code elimination. That sounds like a dangerous heuristic to me because it’s so subtle. I think it’s fair that the package author should declare effectful entry points separately. However I also think webpack could warn when you use such an entry point with a plain import.

That said the way webpack handles CSS in this case doesn’t make sense to me. See facebook/create-react-app#5141 (comment). When you import CSS package library can’t “know” how you’ll “interpret” it. So it can’t know whether it will become a side effect or not. The real effect here is in the style loader. So that leads me to thinking loaders should be able to mark their output as effectful.

@rtsao
Copy link
Contributor

rtsao commented Sep 28, 2018

It would be nice if the entire npm ecosystem adopted the sideEffects package.json field, but realistically, I think it will be a long time before that is the case.

Consider the following scenario:

  • The top-level configuration of webpack has been set tosideEffects: false.
  • Package "core-js" is explicitly imported for side effects (i.e. import "core-js";) and omits a sideEffects field in its package.json

In this scenario, I think pruning "core-js" is clearly incorrect behavior. I certainly don't think import form should be the only heuristic for determining dead code elimination, but in this specific case where a package's side effectfulness is not explicitly specified, not using that information seems just plain wrong.

Until a majority of the npm ecosystem specifies a sideEffects field, it would be nice to allow users to have a default behavior of pruning unused imports (which corresponds to the vast majority of packages) but allow unambiguously effectful imports (import "foo";) to be preserved without extra configuration.

@gaearon
Copy link
Contributor

gaearon commented Sep 28, 2018

@rtsao I think in the scenario you're describing treating such imports specially only adds to the overall confusion. Because now some cases will work and some wouldn't, but the problem will get less widespread and thus there'll be less incentives to properly fix it. So it could cause more pain in longer term.

Your solution is probably reasonable for the behavior when the user explicitly sets top-level sideEffects to false (which I didn't even know is possible — how can the user know everything about their dependencies? this sounds like a dangerous option that most people will misunderstand).

But for cases when you don't have anything on the top level, I don't think plain import should be treated as effectful in contradiction to package's own explicit declaration. For those cases IMO webpack should warn but still respect the package declaration. Otherwise it's too unpredictable.

@dcporter
Copy link

The import “foo” form is an extremely clear indication of developer intent. There is no case in which one uses that import form and doesn’t want it included (assuming the importing file is included) — that’s what that form is for, and it’s the only thing it’s for. That clear, language-level developer declaration should in all cases override the sideEffects flag, and not just for now—broad, clear understanding and use of the flag won’t change the clarity of developer intent when importing a module without symbol.

@gaearon
Copy link
Contributor

gaearon commented Sep 29, 2018

The reason I'm opposed to this is because it's unexpected to see refactoring from

import 'foo'

to

import {x} from 'foo'

break code.

For example, somebody might be running some code just for side effects, relying on your proposed behavior. Then they actually need to add an import, and it's super easy to miss that adding an import would remove the side effects in production only.

Of course you can have two import statements but then that is unexpected too.

import 'foo' 
import {x} from 'foo'

Anybody can refactor this to "remove duplicate import" (in fact there's probably linter plugins to do that or complain about that). It's too subtle for this to change the behavior.

We need to consider not just how the code looks, but how people tend to change it over time, and anticipate the pitfalls.

This is why I think webpack should warn when you use plain import (correctly guessing your intent, and surfacing the problem to you). But I don't think it should change the behavior. Because the fix should be in the package that "lies" about its declared side effects. Warning surfaces the problem, package author fixes it, and everybody is happy.

@sokra
Copy link
Member

sokra commented Sep 29, 2018

A warning is definitely a good idea.

Using import "x" when x is sideeffect-free seems to be wrong to me, either x isn't really sideeffect-free and the package.json is incorrect or the import should be removed because it has no effect. So accepting import "x" as forced inclusion doesn't seem to be a valid option to me.

@sokra
Copy link
Member

sokra commented Sep 29, 2018

Some note about adding sideEffects: false to your application. This will not inherit to the node_modules. Only the nearest package.json in the tree is used to read the flag. -> flag affects only the package and not dependencies

@sokra
Copy link
Member

sokra commented Sep 29, 2018

Regarding importing CSS. While the exact way how CSS is handled is maybe not defined, the semantic of import "file.css" is defined. It means: "the rules in the file become somehow effective". And the semantic is also the thing that defines if it has sideeffects, which is has. So a module can flag CSS as sideeffect-full even if it doesn't know how it's handled exactly.

Anyway, you can also set sideEffects in the CSS rule in module.rules.

@gaearon
Copy link
Contributor

gaearon commented Sep 29, 2018

Thanks, this makes sense!

@gaearon
Copy link
Contributor

gaearon commented Sep 29, 2018

Anyway, you can also set sideEffects in the CSS rule in module.rules.

Do you feel it would be a reasonable default though? We need to decide what CRA should do in facebook/create-react-app#5140.

@dcporter
Copy link

dcporter commented Sep 29, 2018

Using import "x" when x is sideeffect-free seems to be wrong to me

If the developer wrote import “x”, the developer said to include it for its side effects, full stop. Webpack shouldn’t be thinking any more about it than that. (Again, assuming that the importing file is included.) If the developer got that wrong, that’s fine, they get an extra file that they explicitly asked for. Again, accepting it as forced inclusion is what that form exists for, and is the only thing it exists for.

it's unexpected to see refactoring from

import 'foo'

to

import {x} from 'foo'

break code.

I agree that’s a surprising result, and I agree that having two imports is gross (and probably lint-breaking). But the refactored code is incorrect (and is therefore not a refactor): importing x and not using it is a mistake, and isn’t the same as importing foo for side effects. I’d also expect it to be a rarely-sprung trap: most side effect files (e.g. CSS files) have no exports. So breaking a language-level intuition to protect developers from the results of a rare, double mistake, feels like a pretty bad trade-off to me.

Anecdotally, every coworker whose opinion I have initially expects side-effect imports to work with sideEffects: false. (Similarly unscientific: my post above has ten 👍s.) The idea that the side-effect import should work is a simple language-level intuition, which is correct inasmuch as it’s backed up by MDN. sideEffects: false should honor it, and can do so without being less useful or powerful.

@sokra
Copy link
Member

sokra commented Sep 29, 2018

Do you feel it would be a reasonable default though?

Sideeffect-full is the default. You opt-in into sideeffect-free with the package.json-flag.

Being always sideeffect-full for CSS feels like not respecting the opt-in in the package.json and promoting an incorrect sideEffects config in package.json.

Note that CSS modules can be flagged as sideeffect-free.

@sokra
Copy link
Member

sokra commented Sep 29, 2018

If the developer wrote import “x”, the developer said to include it for its side effects, full stop.

If the developer wrote "sideEffects": false, the developer said that it has no side effects, full stop.

These are two conflicting statements the developer made here. Maybe we should even emit an error here.

@dcporter
Copy link

Anecdotally, every coworker whose opinion I have initially expects side-effect imports to work with sideEffects: false.

(These are folks who get what the flag is for and think it’s awesome, by the way.)

@dcporter
Copy link

dcporter commented Sep 29, 2018

These are two conflicting statements the developer made here. Maybe we should even emit an error here.

They are not: they’re intuitively compatible (“put me in no-side-effects mode and let me clearly declare side effects the usual JS-y way”), and could be made actually compatible if you slightly change the semantics of sideEffects: false to match this common intuition. This change would not dilute the meaning or utility of this excellent flag.

I would definitely support adding an error over the status quo of silently stripping them against the developer’s clear intentions. But it would be a really silly error. “We know what you meant here, you couldn’t have meant anything else, but error.”

@gaearon
Copy link
Contributor

gaearon commented Oct 1, 2018

While the exact way how CSS is handled is maybe not defined, the semantic of import "file.css" is defined. It means: "the rules in the file become somehow effective". And the semantic is also the thing that defines if it has sideeffects, which is has. So a module can flag CSS as sideeffect-full even if it doesn't know how it's handled exactly.

Based on this, it seems like webpack could always error if you import CSS from package that says it has no side effects? Since it's never correct.

@gaearon
Copy link
Contributor

gaearon commented Oct 1, 2018

In Create React App, we've decided to always treat css/sass/scss as side effectful in the module rules. If webpack adds a warning (or better, an error) when CSS is marked as side effect-less, we can remove this customization. But for now it's too subtle and would bite people in production otherwise.

@strass
Copy link

strass commented Aug 4, 2020

I agree that this is a cruel trap and that it should at least be consistent between dev and prod

@scamden
Copy link
Contributor

scamden commented Apr 2, 2021

@dcporter

I don't believe webpack does sub-module tree shaking,

Are you sure about that? Perhaps it is due to two different optimizations (usedExports vs sideEffects) but if I add an exported function to a module and don't import it webpack definitely drops it.

@sokra
Copy link
Member

sokra commented Apr 2, 2021

I agree that this is a cruel trap and that it should at least be consistent between dev and prod

In webpack 5 the side effects optimization is enabled for both modes to reduce differences.

@scamden
Copy link
Contributor

scamden commented Apr 2, 2021

@sokra do the marked side effects also get dropped even without minification?

@sokra
Copy link
Member

sokra commented Apr 2, 2021

Yes

@ghost
Copy link

ghost commented May 21, 2021

Wait, so was this feature actually implemented? Is it safe to use:

import 'file.css'
// or
import 'file.js'

// With { sideEffects: false } in package.json?
// -------------
// Will the contents of file.js appear in the bundle?

@Andarist
Copy link
Contributor Author

Will the contents of file.js appear in the bundle?

No, since you have configured sideEffects: false and no import from that file ends up being used.

dereknelson added a commit to CommE2E/comm that referenced this issue Sep 26, 2022

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
… being added in production

Summary: RainbowKit's CSS doesn't get bundled in production, because the way they're exposing it is with a sideEffect. Webpack's side effects are basically an infinite black hole, as denoted by [[ webpack/webpack#6571 | this issue ]] that's been open for 4.5 years. more context/explanation on [[ https://stackoverflow.com/a/49203452 | this stack overflow answer  ]]

Test Plan: run `yarn prod` in `landing` with this diff and RainbowKit experience found on `localhost/commlanding/siwe` now has more than 0 styles

Reviewers: atul, varun, abosh, ashoat

Reviewed By: ashoat

Subscribers: tomek, abosh, varun, ashoat, atul

Differential Revision: https://phab.comm.dev/D5224
jakub-kedra-swm pushed a commit to CommE2E/comm that referenced this issue Sep 28, 2022
… being added in production

Summary: RainbowKit's CSS doesn't get bundled in production, because the way they're exposing it is with a sideEffect. Webpack's side effects are basically an infinite black hole, as denoted by [[ webpack/webpack#6571 | this issue ]] that's been open for 4.5 years. more context/explanation on [[ https://stackoverflow.com/a/49203452 | this stack overflow answer  ]]

Test Plan: run `yarn prod` in `landing` with this diff and RainbowKit experience found on `localhost/commlanding/siwe` now has more than 0 styles

Reviewers: atul, varun, abosh, ashoat

Reviewed By: ashoat

Subscribers: tomek, abosh, varun, ashoat, atul

Differential Revision: https://phab.comm.dev/D5224
@benjamingr
Copy link

benjamingr commented Dec 18, 2022

Like @gaearon this seems like it should be up to the loader or plugins shouldn't it?

Is there a way to explicitly mark a module as "having side effects" from a loader/plugin currently?

Edit: My (awful) solution was to write a ts transform (or a loader if not using TS) to rewrite these import statements and use them in a way that webpack doesn't understand which is a pretty bad hack.

@alfaproject
Copy link

I also agree with @Andarist and was a bit surprised by this today when I decided to finally enable the side effects flag for our monorepo. Tree shaking is way better but alas it also dropped the naked imports which I thought would be enough of a flag for webpack to leave them alone by default )':

@sachinlala
Copy link

Is there a new update on this or a solution ?

@olsonpm
Copy link

olsonpm commented Dec 24, 2024

I disagree the behavior of webpack should differ from a module's configured "sideEffects" flag. Implicit overrides to an explicit configuration would make things even more confusing than they already are.

@dcporter
Copy link

dcporter commented Feb 5, 2025

@alexander-akait great news, curious when/where this was this completed?

@alexander-akait
Copy link
Member

@dcporter What we should to solve here? Answer is here - #6571 (comment)

@dcporter
Copy link

dcporter commented Feb 5, 2025

scrolls down You mean way up above the eighty other subsequent comments? What should be solved is webpack considering its made-up config to be superior to most clear possible statement of intent from the JavaScript language itself.

@alfaproject
Copy link

Or at least opt-in to something implicit to keep those imports:

{
  "sideEffects": "implicit"
}

@alexander-akait
Copy link
Member

alexander-akait commented Feb 5, 2025

@dcporter We use such approach a lot of time (since 2018), changing it right now will be very destructive (a lot of other tools use the same approach) and a lot of project will lose this optimization (bigger size).

By default we use this https://github.com/webpack/webpack/blob/main/lib/config/defaults.js#L1464 for webpack@5, i.e. webpack respects sideEffect in package.json for dev and prod env and you will not faced with different output. If you set sideEffects: false, you clearly say - all my imports are side effect. sideEffects is special thing for bundling where you should clearly indicate where there is a side effects where there is none.

I am not against implementing hints or warnings about potential places, but no one has tried to implement it so far, so I think it is not so popular, and therefore closed the issue.

Where and which do you have a real problem right now?

@alfaproject

Or at least opt-in to something implicit to keep those imports:

This idea is good, but we have vite/esbuild/rspack/etc and they don't support this value, integrate it can be very long and I don't think library authors really can use it someday (don't forget about enterprise that may use the old version for a long time).

I don't think this is a problem specifically with webpack now, although it was originally implemented by webpack, change the behavior or add new values will require coordination of all the authors of the tools, a long time for implementation, releasing, updating, and then a long time for authors.

@dcporter
Copy link

dcporter commented Feb 5, 2025

Where and which do you have a real problem right now?

Approaching 100% of greenfield projects set up by non webpack experts have this real problem. (It's not as atrocious as it used to be, because now you have to fix it up front instead of discovering it just before you ship.)

a lot of project will lose this optimization

I disagree that it would be unduly disruptive to fix this. The deoptimization would be rare since most extant projects have been forced to work through it.

a lot of other tools use the same approach

Other bundlers have implemented side-effect flags whose defaults ignore JavaScript semantics? Which ones?

@alexander-akait
Copy link
Member

Approaching 100% of greenfield projects set up by non webpack experts have this real problem. (It's not as atrocious as it used to be, because now you have to fix it up front instead of discovering it just before you ship.)

What projects? If you don't have sideEffect you will not have problems, but if you use it, please read docs how it works

I disagree that it would be unduly disruptive to fix this. The deoptimization would be rare since most extant projects have been forced to work through it.

I disagree too, it is not rare and I faced with it a lot of time, and a lot of time in big projects, I suggest you provide constructive arguments, rather than stating who agrees or disagrees, this is counter-constructive.

Other bundlers have implemented side-effect flags whose defaults ignore JavaScript semantics? Which ones?

Which not? evanw/esbuild#1241 (comment)

I can provide more link if you want

Once there was a decision to implement this solution like this, yes it may not be perfect and there are certain gaps in the design, but it was done like this, and now changing it will cause huge problems in the entire ecosystem, and I'd rather not touch it, if we want to improve/change/make it more flexible we will need a new field with new structures and values (for example we can take a sideEffectsMapping name where you can provide more values, like Node.js makes with main and exports fields).

If you want to solve this, I suggest you create an RFC, describe your vision and invite the main developers of the tools for discussions, after this is approved we can begin implementation, this is how backward compatibility works and this is how the process in the ecosystem occurs, I can't just change something if it's been working for years just because someone wanted it, sorry.

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