Skip to content

Commit

Permalink
Merge branch 'master' into fix/map-impure
Browse files Browse the repository at this point in the history
  • Loading branch information
TrickyPi committed Apr 24, 2023
2 parents f089270 + 28c7b7c commit 56bf8aa
Show file tree
Hide file tree
Showing 37 changed files with 677 additions and 94 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# rollup changelog

## 3.21.0

_2023-04-23_

### Features

- Support tree-shaking of named exports in dynamic imports when using destructuring and similar patterns (#4952)

### Pull Requests

- [#4952](https://github.com/rollup/rollup/pull/4952): feat: tree-shake deterministic dynamic imports (@antfu)

## 3.20.7

_2023-04-21_
Expand Down
2 changes: 1 addition & 1 deletion browser/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@rollup/browser",
"version": "3.20.7",
"version": "3.21.0",
"description": "Next-generation ES module bundler browser build",
"main": "dist/rollup.browser.js",
"module": "dist/es/rollup.browser.js",
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "rollup",
"version": "3.20.7",
"version": "3.21.0",
"description": "Next-generation ES module bundler",
"main": "dist/rollup.js",
"module": "dist/es/rollup.js",
Expand Down
39 changes: 38 additions & 1 deletion src/Module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -716,6 +716,33 @@ export default class Module {
this.includeAllExports(false);
}

includeExportsByNames(names: readonly string[]): void {
if (!this.isExecuted) {
markModuleAndImpureDependenciesAsExecuted(this);
this.graph.needsTreeshakingPass = true;
}

let includeNamespaceMembers = false;

for (const name of names) {
const variable = this.getVariableForExportName(name)[0];
if (variable) {
variable.deoptimizePath(UNKNOWN_PATH);
if (!variable.included) {
this.includeVariable(variable);
}
}

if (!this.exports.has(name) && !this.reexportDescriptions.has(name)) {
includeNamespaceMembers = true;
}
}

if (includeNamespaceMembers) {
this.namespace.setMergedNamespaces(this.includeAndGetAdditionalMergedNamespaces());
}
}

isIncluded(): boolean | null {
// Modules where this.ast is missing have been loaded via this.load and are
// not yet fully processed, hence they cannot be included.
Expand Down Expand Up @@ -1218,9 +1245,19 @@ export default class Module {
resolution: string | Module | ExternalModule | undefined;
}
).resolution;

if (resolution instanceof Module) {
resolution.includedDynamicImporters.push(this);
resolution.includeAllExports(true);

const importedNames = this.options.treeshake
? node.getDeterministicImportedNames()
: undefined;

if (importedNames) {
resolution.includeExportsByNames(importedNames);
} else {
resolution.includeAllExports(true);
}
}
}

Expand Down
116 changes: 116 additions & 0 deletions src/ast/nodes/ImportExpression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import ExternalModule from '../../ExternalModule';
import type Module from '../../Module';
import type { GetInterop, NormalizedOutputOptions } from '../../rollup/types';
import type { PluginDriver } from '../../utils/PluginDriver';
import { EMPTY_ARRAY } from '../../utils/blank';
import type { GenerateCodeSnippets } from '../../utils/generateCodeSnippets';
import {
INTEROP_NAMESPACE_DEFAULT_ONLY_VARIABLE,
Expand All @@ -12,8 +13,17 @@ import { findFirstOccurrenceOutsideComment, type RenderOptions } from '../../uti
import type { InclusionContext } from '../ExecutionContext';
import type ChildScope from '../scopes/ChildScope';
import type NamespaceVariable from '../variables/NamespaceVariable';
import ArrowFunctionExpression from './ArrowFunctionExpression';
import AwaitExpression from './AwaitExpression';
import CallExpression from './CallExpression';
import ExpressionStatement from './ExpressionStatement';
import FunctionExpression from './FunctionExpression';
import Identifier from './Identifier';
import MemberExpression from './MemberExpression';
import type * as NodeType from './NodeType';
import type ObjectExpression from './ObjectExpression';
import ObjectPattern from './ObjectPattern';
import VariableDeclarator from './VariableDeclarator';
import {
type ExpressionNode,
type GenericEsTreeNode,
Expand Down Expand Up @@ -45,6 +55,100 @@ export default class ImportExpression extends NodeBase {
this.source.bind();
}

/**
* Get imported variables for deterministic usage, valid cases are:
*
* - `const { foo } = await import('bar')`.
* - `(await import('bar')).foo`
* - `import('bar').then(({ foo }) => {})`
*
* Returns empty array if it's side-effect only import.
* Returns undefined if it's not fully deterministic.
*/
getDeterministicImportedNames(): readonly string[] | undefined {
const parent1 = this.parent;

// Side-effect only: import('bar')
if (parent1 instanceof ExpressionStatement) {
return EMPTY_ARRAY;
}

if (parent1 instanceof AwaitExpression) {
const parent2 = parent1.parent;

// Side-effect only: await import('bar')
if (parent2 instanceof ExpressionStatement) {
return EMPTY_ARRAY;
}

// Case 1: const { foo } = await import('bar')
if (parent2 instanceof VariableDeclarator) {
const declaration = parent2.id;
return declaration instanceof ObjectPattern
? getDeterministicObjectDestructure(declaration)
: undefined;
}

// Case 2: (await import('bar')).foo
if (parent2 instanceof MemberExpression) {
const id = parent2.property;
if (!parent2.computed && id instanceof Identifier) {
return [id.name];
}
}

return;
}

// Case 3: import('bar').then(({ foo }) => {})
if (parent1 instanceof MemberExpression) {
const callExpression = parent1.parent;
const property = parent1.property;

if (!(callExpression instanceof CallExpression) || !(property instanceof Identifier)) {
return;
}

const memberName = property.name;

// side-effect only, when only chaining .catch or .finally
if (
callExpression.parent instanceof ExpressionStatement &&
['catch', 'finally'].includes(memberName)
) {
return EMPTY_ARRAY;
}

if (memberName !== 'then') return;

// Side-effect only: import('bar').then()
if (callExpression.arguments.length === 0) {
return EMPTY_ARRAY;
}

const argument = callExpression.arguments[0];

if (
callExpression.arguments.length !== 1 ||
!(argument instanceof ArrowFunctionExpression || argument instanceof FunctionExpression)
) {
return;
}

// Side-effect only: import('bar').then(() => {})
if (argument.params.length === 0) {
return EMPTY_ARRAY;
}

const declaration = argument.params[0];
if (argument.params.length === 1 && declaration instanceof ObjectPattern) {
return getDeterministicObjectDestructure(declaration);
}

return;
}
}

hasEffects(): boolean {
return true;
}
Expand Down Expand Up @@ -296,3 +400,15 @@ const accessedImportGlobals: Record<string, string[]> = {
cjs: ['require'],
system: ['module']
};

function getDeterministicObjectDestructure(objectPattern: ObjectPattern): string[] | undefined {
const variables: string[] = [];

for (const property of objectPattern.properties) {
if (property.type === 'RestElement' || property.computed || property.key.type !== 'Identifier')
return;
variables.push((property.key as Identifier).name);
}

return variables;
}
8 changes: 4 additions & 4 deletions src/ast/variables/NamespaceVariable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,9 @@ export default class NamespaceVariable extends Variable {
snippets: { _, cnst, getObject, getPropertyAccess, n, s }
} = options;
const memberVariables = this.getMemberVariables();
const members: [key: string | null, value: string][] = Object.entries(memberVariables).map(
([name, original]) => {
const members: [key: string | null, value: string][] = Object.entries(memberVariables)
.filter(([_, variable]) => variable.included)
.map(([name, original]) => {
if (this.referencedEarly || original.isReassigned) {
return [
null,
Expand All @@ -143,8 +144,7 @@ export default class NamespaceVariable extends Variable {
}

return [name, original.getName(getPropertyAccess)];
}
);
});
members.unshift([null, `__proto__:${_}null`]);

let output = getObject(members, { lineBreakIndent: { base: '', t } });
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
define(['exports'], (function (exports) { 'use strict';

const bar = 2;
Promise.resolve().then(function () { return foo$1; });
Promise.resolve().then(function () { return foo; });

const foo = 1;

var foo$1 = /*#__PURE__*/Object.freeze({
__proto__: null,
foo: foo
var foo = /*#__PURE__*/Object.freeze({
__proto__: null
});

exports.bar = bar;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
'use strict';

const bar = 2;
Promise.resolve().then(function () { return foo$1; });
Promise.resolve().then(function () { return foo; });

const foo = 1;

var foo$1 = /*#__PURE__*/Object.freeze({
__proto__: null,
foo: foo
var foo = /*#__PURE__*/Object.freeze({
__proto__: null
});

exports.bar = bar;
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
const bar = 2;
Promise.resolve().then(function () { return foo$1; });
Promise.resolve().then(function () { return foo; });

const foo = 1;

var foo$1 = /*#__PURE__*/Object.freeze({
__proto__: null,
foo: foo
var foo = /*#__PURE__*/Object.freeze({
__proto__: null
});

export { bar };
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,10 @@ System.register([], (function (exports) {
execute: (function () {

const bar = exports('bar', 2);
Promise.resolve().then(function () { return foo$1; });
Promise.resolve().then(function () { return foo; });

const foo = 1;

var foo$1 = /*#__PURE__*/Object.freeze({
__proto__: null,
foo: foo
var foo = /*#__PURE__*/Object.freeze({
__proto__: null
});

})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
define(['exports'], (function (exports) { 'use strict';

const bar = 2;
Promise.resolve().then(function () { return foo$1; });
Promise.resolve().then(function () { return foo; });

const foo = 1;

var foo$1 = /*#__PURE__*/Object.freeze({
__proto__: null,
foo: foo
var foo = /*#__PURE__*/Object.freeze({
__proto__: null
});

exports.bar = bar;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
'use strict';

const bar = 2;
Promise.resolve().then(function () { return foo$1; });
Promise.resolve().then(function () { return foo; });

const foo = 1;

var foo$1 = /*#__PURE__*/Object.freeze({
__proto__: null,
foo: foo
var foo = /*#__PURE__*/Object.freeze({
__proto__: null
});

exports.bar = bar;
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
const bar = 2;
Promise.resolve().then(function () { return foo$1; });
Promise.resolve().then(function () { return foo; });

const foo = 1;

var foo$1 = /*#__PURE__*/Object.freeze({
__proto__: null,
foo: foo
var foo = /*#__PURE__*/Object.freeze({
__proto__: null
});

export { bar };

0 comments on commit 56bf8aa

Please sign in to comment.