Skip to content

Commit

Permalink
Make return expressions of pure functions pure
Browse files Browse the repository at this point in the history
  • Loading branch information
lukastaegert committed Nov 22, 2022
1 parent 8daf05e commit cc53d89
Show file tree
Hide file tree
Showing 7 changed files with 67 additions and 37 deletions.
8 changes: 5 additions & 3 deletions docs/999-big-list-of-options.md
Expand Up @@ -1712,12 +1712,14 @@ Allows to manually define a list of function names that should always be conside
This can not only help with dead code removal, but can also improve JavaScript chunk generation especially when using [`experimentalMinChunkSize`](guide/en/#experimentalminchunksize).
Besides any functions matching that name, any properties on a pure function and any functions returned from a pure functions will also be considered pure.
```js
// rollup.config.js
export default {
treeshake: {
preset: 'smallest',
manualPureFunctions: ['styled.div', 'local']
manualPureFunctions: ['styled', 'local']
}
// ...
};
Expand All @@ -1731,8 +1733,8 @@ styled.div`
color: blue;
`; // removed
styled?.div(); // removed
styled(); // not removed
styled.h1(); // not removed
styled()(); // removed
styled().div(); // removed
```
**treeshake.moduleSideEffects**<br> Type: `boolean | "no-external" | string[] | (id: string, external: boolean) => boolean`<br> CLI: `--treeshake.moduleSideEffects`/`--no-treeshake.moduleSideEffects`/`--treeshake.moduleSideEffects no-external`<br> Default: `true`
Expand Down
17 changes: 10 additions & 7 deletions src/ast/nodes/Identifier.ts
Expand Up @@ -133,12 +133,14 @@ export default class Identifier extends NodeBase implements PatternNode {
recursionTracker: PathTracker,
origin: DeoptimizableEntity
): [expression: ExpressionEntity, isPure: boolean] {
return this.getVariableRespectingTDZ()!.getReturnExpressionWhenCalledAtPath(
path,
interaction,
recursionTracker,
origin
);
const [expression, isPure] =
this.getVariableRespectingTDZ()!.getReturnExpressionWhenCalledAtPath(
path,
interaction,
recursionTracker,
origin
);
return [expression, isPure || this.isPureFunction(path)];
}

hasEffects(context: HasEffectsContext): boolean {
Expand All @@ -149,6 +151,7 @@ export default class Identifier extends NodeBase implements PatternNode {
return (
(this.context.options.treeshake as NormalizedTreeshakingOptions).unknownGlobalSideEffects &&
this.variable instanceof GlobalVariable &&
!this.isPureFunction(EMPTY_PATH) &&
this.variable.hasEffectsOnInteractionAtPath(
EMPTY_PATH,
NODE_INTERACTION_UNKNOWN_ACCESS,
Expand Down Expand Up @@ -301,7 +304,7 @@ export default class Identifier extends NodeBase implements PatternNode {
return false;
}
}
return currentPureFunction?.[PureFunctionKey];
return currentPureFunction?.[PureFunctionKey] as boolean;
}
}

Expand Down
16 changes: 10 additions & 6 deletions src/ast/nodes/shared/CallExpressionBase.ts
Expand Up @@ -53,7 +53,6 @@ export default abstract class CallExpressionBase extends NodeBase implements Deo
recursionTracker: PathTracker
): void {
const [returnExpression, isPure] = this.getReturnExpression(recursionTracker);
// TODO Lukas test
if (isPure) return;
if (returnExpression === UNKNOWN_EXPRESSION) {
interaction.thisArg.deoptimizePath(UNKNOWN_PATH);
Expand Down Expand Up @@ -96,21 +95,22 @@ export default abstract class CallExpressionBase extends NodeBase implements Deo
recursionTracker: PathTracker,
origin: DeoptimizableEntity
): [expression: ExpressionEntity, isPure: boolean] {
const [returnExpression] = this.getReturnExpression(recursionTracker);
if (returnExpression === UNKNOWN_EXPRESSION) {
return UNKNOWN_RETURN_EXPRESSION;
const returnExpression = this.getReturnExpression(recursionTracker);
if (returnExpression[0] === UNKNOWN_EXPRESSION) {
return returnExpression;
}
return recursionTracker.withTrackedEntityAtPath(
path,
returnExpression,
() => {
this.deoptimizableDependentExpressions.push(origin);
return returnExpression.getReturnExpressionWhenCalledAtPath(
const [expression, isPure] = returnExpression[0].getReturnExpressionWhenCalledAtPath(
path,
interaction,
recursionTracker,
origin
);
return [expression, isPure || returnExpression[1]];
},
UNKNOWN_RETURN_EXPRESSION
);
Expand Down Expand Up @@ -141,7 +141,11 @@ export default abstract class CallExpressionBase extends NodeBase implements Deo
) {
return false;
}
return this.getReturnExpression()[0].hasEffectsOnInteractionAtPath(path, interaction, context);
const [returnExpression, isPure] = this.getReturnExpression();
return (
(type === INTERACTION_ASSIGNED || !isPure) &&
returnExpression.hasEffectsOnInteractionAtPath(path, interaction, context)
);
}

protected abstract getReturnExpression(
Expand Down
3 changes: 0 additions & 3 deletions test/form/samples/manual-pure-functions/_config.js
@@ -1,9 +1,6 @@
module.exports = {
solo: true,
description: 'allows to manually declare functions as pure by name',
options: {
treeshake: { manualPureFunctions: ['foo', 'bar.a'] }
}
};
// TODO Lukas also tagged templates
// TODO Lukas also check "this" or arguments are not deoptimized
18 changes: 11 additions & 7 deletions test/form/samples/manual-pure-functions/_expected.js
@@ -1,11 +1,15 @@
const lib = () => console.log();
lib.a = () => {
console.log();
const result = () => console.log();
result.c = console.log;
return result;
const lib = {
a: () => {
console.log();
return () => {
console.log();
return () => {
console.log();
return console.log;
}
}
}
};
lib.a.b = () => console.log();

lib(); // not removed
lib.b(); // not removed
24 changes: 20 additions & 4 deletions test/form/samples/manual-pure-functions/main.js
@@ -1,11 +1,27 @@
import { lib as bar } from './other';
const foo = console.log;

foo; // removed
foo(); // removed
bar.a(); // removed
bar?.a(); // removed
foo.a; // removed
foo.a(); // removed
foo.a()(); // removed
foo.a().a; // removed
foo.a().a(); // removed
foo.a().a()(); // removed
foo.a().a().a; // removed
foo.a().a().a(); // removed

bar(); // not removed
bar.b(); // not removed

bar.a.b(); // removed
bar.a(); // removed
bar?.a(); // removed
bar.a.a; // removed
bar.a.a(); // removed
bar.a()(); //removed
bar.a().a; //removed
bar.a().a(); //removed
bar.a()()(); //removed
bar.a()().a; //removed
bar.a()().a(); //removed

18 changes: 11 additions & 7 deletions test/form/samples/manual-pure-functions/other.js
@@ -1,8 +1,12 @@
export const lib = () => console.log();
lib.a = () => {
console.log();
const result = () => console.log();
result.c = console.log;
return result;
export const lib = {
a: () => {
console.log();
return () => {
console.log();
return () => {
console.log();
return console.log;
}
}
}
};
lib.a.b = () => console.log();

0 comments on commit cc53d89

Please sign in to comment.