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

[v2.0.0] Avoid empty imports from side-effect-free chunks #3369

Merged
merged 9 commits into from Feb 8, 2020

Conversation

lukastaegert
Copy link
Member

@lukastaegert lukastaegert commented Feb 5, 2020

This PR contains:

  • bugfix
  • feature
  • refactor
  • documentation
  • other

Are tests included?

  • yes (bugfixes and features will not be merged without tests)
  • no

Breaking Changes?

  • yes (breaking changes will not be merged unless absolutely necessary)
  • no

List any relevant issue numbers:
Resolves #3359
Resolves #3109
Resolves #3057

Description

Originally the goal with this PR was to just suppress empty imports from chunks if moduleSideEffects is false. However as it turned out, it was possible to extend this beyond the moduleSideEffects flag by using the existing tree-shaking mechanics to infer this flag for modules for which it was not explicitly set to false; moreover, Rollup will also generate fewer chunks in many situations.

To do this right, I implemented this on module level, not on chunk level. This means that after tree-shaking, each "empty" dependency (i.e. import where no bindings are actually imported) of a module is tested for side-effects and

  • is removed if moduleSideEffects is false for this dependency
  • is recursively replaced by its side-effectful dependencies if moduleSideEffects is true but no side-effects can be found inside the module
  • is retained otherwise

This happens BEFORE chunks are generated, which means that after this PR, more efficient chunks will be generated! As an example, consider the following:

// input
// main1.js
import dep from './dep.js';
dep();

// main2.js
import dep from './dep.js';
console.log('main2');

// dep.js
export default function onlyUsedByOne() {
	console.log('Hello');
}

If you run this in the REPL, you will get three chunks with an empty import from the main2 chunk to the shared chunk–even though the shared chunk does not have side-effects: https://rollupjs.org/repl/?version=1.31.0&shareable=JTdCJTIybW9kdWxlcyUyMiUzQSU1QiU3QiUyMm5hbWUlMjIlM0ElMjJtYWluLmpzJTIyJTJDJTIyY29kZSUyMiUzQSUyMmltcG9ydCUyMGRlcCUyMGZyb20lMjAnLiUyRmRlcC5qcyclM0IlNUNuZGVwKCklM0IlMjIlMkMlMjJpc0VudHJ5JTIyJTNBdHJ1ZSU3RCUyQyU3QiUyMm5hbWUlMjIlM0ElMjJtYWluMi5qcyUyMiUyQyUyMmNvZGUlMjIlM0ElMjJpbXBvcnQlMjBkZXAlMjBmcm9tJTIwJy4lMkZkZXAuanMnJTNCJTVDbmNvbnNvbGUubG9nKCdtYWluMicpJTNCJTIyJTJDJTIyaXNFbnRyeSUyMiUzQXRydWUlN0QlMkMlN0IlMjJuYW1lJTIyJTNBJTIyZGVwLmpzJTIyJTJDJTIyY29kZSUyMiUzQSUyMmV4cG9ydCUyMGRlZmF1bHQlMjBmdW5jdGlvbiUyMG9ubHlVc2VkQnlPbmUoKSUyMCU3QiU1Q24lNUN0Y29uc29sZS5sb2coJ0hlbGxvJyklM0IlNUNuJTdEJTIyJTJDJTIyaXNFbnRyeSUyMiUzQWZhbHNlJTdEJTVEJTJDJTIyb3B0aW9ucyUyMiUzQSU3QiUyMmZvcm1hdCUyMiUzQSUyMmVzbSUyMiUyQyUyMm5hbWUlMjIlM0ElMjJteUJ1bmRsZSUyMiUyQyUyMmFtZCUyMiUzQSU3QiUyMmlkJTIyJTNBJTIyJTIyJTdEJTJDJTIyZ2xvYmFscyUyMiUzQSU3QiU3RCU3RCUyQyUyMmV4YW1wbGUlMjIlM0FudWxsJTdE

With this PR, you will instead just get the following two chunks:

// output
// main1.js
function onlyUsedByOne() {
	console.log('Hello');
}
onlyUsedByOne();

// main2.js
console.log('main2');

You can check this out here: https://rollupjs.org/repl/?circleci=9330&shareable=JTdCJTIybW9kdWxlcyUyMiUzQSU1QiU3QiUyMm5hbWUlMjIlM0ElMjJtYWluLmpzJTIyJTJDJTIyY29kZSUyMiUzQSUyMmltcG9ydCUyMGRlcCUyMGZyb20lMjAnLiUyRmRlcC5qcyclM0IlNUNuZGVwKCklM0IlMjIlMkMlMjJpc0VudHJ5JTIyJTNBdHJ1ZSU3RCUyQyU3QiUyMm5hbWUlMjIlM0ElMjJtYWluMi5qcyUyMiUyQyUyMmNvZGUlMjIlM0ElMjJpbXBvcnQlMjBkZXAlMjBmcm9tJTIwJy4lMkZkZXAuanMnJTNCJTVDbmNvbnNvbGUubG9nKCdtYWluMicpJTNCJTIyJTJDJTIyaXNFbnRyeSUyMiUzQXRydWUlN0QlMkMlN0IlMjJuYW1lJTIyJTNBJTIyZGVwLmpzJTIyJTJDJTIyY29kZSUyMiUzQSUyMmV4cG9ydCUyMGRlZmF1bHQlMjBmdW5jdGlvbiUyMG9ubHlVc2VkQnlPbmUoKSUyMCU3QiU1Q24lNUN0Y29uc29sZS5sb2coJ0hlbGxvJyklM0IlNUNuJTdEJTIyJTJDJTIyaXNFbnRyeSUyMiUzQWZhbHNlJTdEJTVEJTJDJTIyb3B0aW9ucyUyMiUzQSU3QiUyMmZvcm1hdCUyMiUzQSUyMmVzbSUyMiUyQyUyMm5hbWUlMjIlM0ElMjJteUJ1bmRsZSUyMiUyQyUyMmFtZCUyMiUzQSU3QiUyMmlkJTIyJTNBJTIyJTIyJTdEJTJDJTIyZ2xvYmFscyUyMiUzQSU3QiU3RCU3RCUyQyUyMmV4YW1wbGUlMjIlM0FudWxsJTdE

This means you will not only get fewer chunks but the second chunk will also avoid loading quite a bit of maybe-not-dead-but-definitely-not-needed code.

Now assume that dep.js itself has a side-effectful dependency, for instance an external import:

// input
// dep.js
import 'external-side-effect';
export default function onlyUsedByOne() {
	console.log('Hello');
}

Then you will STILL get two chunks, but the side-effect import will be hoisted to both chunks:

// output
// main1.js
import 'external-side-effect';
function onlyUsedByOne() {
	console.log('Hello');
}
onlyUsedByOne();

// main2.js
import 'external-side-effect';
console.log('main2');

I think this is a great improvement for everyone and the first step to actually fixing many ordering issues we can currently have with code-splitting without creating endless amounts of chunks as side-effect-free modules can be moved around rather safely (not done in this PR yet, though).

I have marked this as "BREAKING" because with this change, side-effect-free modules will no longer appear as part of any chunk in the generated bundle object. Previously, they would always appear to be assigned to some chunk. I am not sure if there are tools that analyze tree-shaking quality that depend on all modules being mentioned in the output.

@codecov
Copy link

codecov bot commented Feb 5, 2020

Codecov Report

Merging #3369 into release-2.0.0 will increase coverage by 0.15%.
The diff coverage is 100%.

Impacted file tree graph

@@                Coverage Diff                @@
##           release-2.0.0    #3369      +/-   ##
=================================================
+ Coverage          94.07%   94.23%   +0.15%     
=================================================
  Files                174      173       -1     
  Lines               5977     5950      -27     
  Branches            1768     1757      -11     
=================================================
- Hits                5623     5607      -16     
+ Misses               191      190       -1     
+ Partials             163      153      -10
Impacted Files Coverage Δ
src/utils/deconflictChunk.ts 100% <ø> (ø) ⬆️
src/Graph.ts 97.35% <100%> (-0.17%) ⬇️
src/utils/executionOrder.ts 100% <100%> (+2.17%) ⬆️
src/Module.ts 97.93% <100%> (+0.39%) ⬆️
src/utils/chunkAssignment.ts 100% <100%> (ø)
src/ast/nodes/Program.ts 100% <100%> (ø) ⬆️
src/Chunk.ts 95.07% <100%> (+1.09%) ⬆️
src/chunk-optimization.ts 57.35% <0%> (+1.47%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update cfdc86b...b335238. Read the comment docs.

@lukastaegert lukastaegert force-pushed the gh-3359-chunk-module-side-effects branch from dd4625a to 1efe9f6 Compare February 5, 2020 06:24
@lukastaegert lukastaegert added this to In progress in Release 2.0.0 via automation Feb 5, 2020
@lukastaegert lukastaegert moved this from In progress to Ready for merge in Release 2.0.0 Feb 5, 2020
@lukastaegert lukastaegert force-pushed the gh-3359-chunk-module-side-effects branch 11 times, most recently from 4a87438 to da9a869 Compare February 7, 2020 14:35
@LarsDenBakker
Copy link
Contributor

This is a huge deal for apps with a lot of dynamic imports. Would it be possible the provide this behind a flag in 1.x? Or is 2.x already close to shipping?

@lukastaegert
Copy link
Member Author

lukastaegert commented Feb 7, 2020

@LarsDenBakker

Or is 2.x already close to shipping?

Actually really close https://github.com/rollup/rollup/projects/2, the only remaining thing would be aligning on and implementing #3370 . And possibly making a call on #3343: experimentalOptimizeChunks is buggy and makes refactoring and future innovations hard, I think I really want to get rid of it.

Well that's cool as hell. 👍🏻👍🏻👍🏻

I spent some inspirational time today thinking that we finally should have a bot and be able to try out PRs in the REPL 😜

@lukastaegert lukastaegert force-pushed the gh-3359-chunk-module-side-effects branch from da9a869 to b335238 Compare February 7, 2020 19:52
@rollup-bot
Copy link
Collaborator

Thank you for your contribution! ❤️

You can try out this pull request locally via

npm install rollup/rollup#gh-3359-chunk-module-side-effects

or load it into the REPL:
https://rollupjs.org/repl/?circleci=9337

@tivac
Copy link
Contributor

tivac commented Feb 7, 2020

I spent some inspirational time today thinking that we finally should have a bot and be able to try out PRs in the REPL 😜

Being able to easily try out big refactors by poking at them in the REPL or even installing them into existing projects is hugely helpful in terms of being able to provide real feedback. Thank you!

@lukeed
Copy link
Contributor

lukeed commented Feb 7, 2020

Awesome work! 👏 It seems like this would also open up #2566 for a solution, right?

@lukastaegert
Copy link
Member Author

There is not really a connection, but with this change, fixing #2566 would also benefit code-splitting.

@lukastaegert lukastaegert merged commit d19c51a into release-2.0.0 Feb 8, 2020
Release 2.0.0 automation moved this from Ready for merge to Done Feb 8, 2020
@lukastaegert lukastaegert deleted the gh-3359-chunk-module-side-effects branch February 8, 2020 19:10
@lukastaegert lukastaegert mentioned this pull request Feb 8, 2020
9 tasks
lukastaegert added a commit that referenced this pull request Feb 8, 2020
* Respect moduleSideEffects when inlining empty imports

* Make dependencies a Set

* Also handle side-effect free reexports correctly without generating separate chunks

* Infer side-effect free modules and hoist side-effects

* Simplify some logic

* Better encapsulate chunk assignment

* Get rid of colouring hashes

* Improve coverage

* Store the build output as artifact and post an automated comment
lukastaegert added a commit that referenced this pull request Feb 14, 2020
* Respect moduleSideEffects when inlining empty imports

* Make dependencies a Set

* Also handle side-effect free reexports correctly without generating separate chunks

* Infer side-effect free modules and hoist side-effects

* Simplify some logic

* Better encapsulate chunk assignment

* Get rid of colouring hashes

* Improve coverage

* Store the build output as artifact and post an automated comment
lukastaegert added a commit that referenced this pull request Feb 19, 2020
* Respect moduleSideEffects when inlining empty imports

* Make dependencies a Set

* Also handle side-effect free reexports correctly without generating separate chunks

* Infer side-effect free modules and hoist side-effects

* Simplify some logic

* Better encapsulate chunk assignment

* Get rid of colouring hashes

* Improve coverage

* Store the build output as artifact and post an automated comment
lukastaegert added a commit that referenced this pull request Feb 26, 2020
* Respect moduleSideEffects when inlining empty imports

* Make dependencies a Set

* Also handle side-effect free reexports correctly without generating separate chunks

* Infer side-effect free modules and hoist side-effects

* Simplify some logic

* Better encapsulate chunk assignment

* Get rid of colouring hashes

* Improve coverage

* Store the build output as artifact and post an automated comment
lukastaegert added a commit that referenced this pull request Mar 6, 2020
* Respect moduleSideEffects when inlining empty imports

* Make dependencies a Set

* Also handle side-effect free reexports correctly without generating separate chunks

* Infer side-effect free modules and hoist side-effects

* Simplify some logic

* Better encapsulate chunk assignment

* Get rid of colouring hashes

* Improve coverage

* Store the build output as artifact and post an automated comment
lukastaegert added a commit that referenced this pull request Mar 6, 2020
* Respect moduleSideEffects when inlining empty imports

* Make dependencies a Set

* Also handle side-effect free reexports correctly without generating separate chunks

* Infer side-effect free modules and hoist side-effects

* Simplify some logic

* Better encapsulate chunk assignment

* Get rid of colouring hashes

* Improve coverage

* Store the build output as artifact and post an automated comment
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
No open projects
Release 2.0.0
  
Done
Development

Successfully merging this pull request may close these issues.

None yet

5 participants