Skip to content

Commit 94c96fa

Browse files
authored
Keep attributes for external dynamic imports (#6071)
* Add test * Binding attributes for unresolved dynamic imports * Improve the implementation * Move this.options.includePath into includeNode
1 parent e8e9f83 commit 94c96fa

File tree

22 files changed

+168
-24
lines changed

22 files changed

+168
-24
lines changed

src/Chunk.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1018,15 +1018,19 @@ export default class Chunk {
10181018
fileName: string,
10191019
node: ImportExpression
10201020
): [importPath: string, attributes: string | null | true] {
1021+
const { externalImportAttributes } = this.outputOptions;
1022+
const keepExternalImportAttributes =
1023+
['es', 'cjs'].includes(this.outputOptions.format) && externalImportAttributes;
10211024
if (resolution instanceof ExternalModule) {
10221025
const chunk = this.externalChunkByModule.get(resolution)!;
1023-
return [`'${chunk.getImportPath(fileName)}'`, chunk.getImportAttributes(this.snippets)];
1026+
const dynamicAttributes = chunk.getImportAttributes(this.snippets);
1027+
return [
1028+
`'${chunk.getImportPath(fileName)}'`,
1029+
dynamicAttributes || (keepExternalImportAttributes ? true : null)
1030+
];
10241031
}
10251032
let attributes: string | true | null = null;
1026-
if (
1027-
['es', 'cjs'].includes(this.outputOptions.format) &&
1028-
this.outputOptions.externalImportAttributes
1029-
) {
1033+
if (keepExternalImportAttributes) {
10301034
const attributesFromImportAttributes = getAttributesFromImportExpression(node);
10311035
attributes =
10321036
attributesFromImportAttributes === EMPTY_OBJECT

src/ModuleLoader.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -567,7 +567,10 @@ export class ModuleLoader {
567567
module.id,
568568
getAttributesFromImportExpression(dynamicImport.node)
569569
);
570-
if (resolvedId && typeof resolvedId === 'object') {
570+
if (!resolvedId || typeof resolvedId === 'string') {
571+
dynamicImport.node.shouldIncludeDynamicAttributes = true;
572+
} else {
573+
dynamicImport.node.shouldIncludeDynamicAttributes = !!resolvedId.external;
571574
dynamicImport.id = resolvedId.id;
572575
}
573576
return [dynamicImport, resolvedId] as const;

src/ast/nodes/ImportExpression.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { findFirstOccurrenceOutsideComment, type RenderOptions } from '../../uti
2121
import type { InclusionContext } from '../ExecutionContext';
2222
import type ChildScope from '../scopes/ChildScope';
2323
import type { ObjectPath } from '../utils/PathTracker';
24-
import { UnknownKey } from '../utils/PathTracker';
24+
import { UNKNOWN_PATH, UnknownKey } from '../utils/PathTracker';
2525
import type NamespaceVariable from '../variables/NamespaceVariable';
2626
import ArrowFunctionExpression from './ArrowFunctionExpression';
2727
import AwaitExpression from './AwaitExpression';
@@ -68,6 +68,14 @@ export default class ImportExpression extends NodeBase {
6868
private resolution: Module | ExternalModule | string | null = null;
6969
private resolutionString: string | null = null;
7070

71+
get shouldIncludeDynamicAttributes() {
72+
return isFlagSet(this.flags, Flag.shouldIncludeDynamicAttributes);
73+
}
74+
75+
set shouldIncludeDynamicAttributes(value: boolean) {
76+
this.flags = setFlag(this.flags, Flag.shouldIncludeDynamicAttributes, value);
77+
}
78+
7179
get withinTopLevelAwait() {
7280
return isFlagSet(this.flags, Flag.withinTopLevelAwait);
7381
}
@@ -76,9 +84,9 @@ export default class ImportExpression extends NodeBase {
7684
this.flags = setFlag(this.flags, Flag.withinTopLevelAwait, value);
7785
}
7886

79-
// Do not bind attributes
8087
bind(): void {
8188
this.source.bind();
89+
this.options?.bind();
8290
}
8391

8492
/**
@@ -185,18 +193,21 @@ export default class ImportExpression extends NodeBase {
185193
}
186194

187195
include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void {
188-
if (!this.included) this.includeNode();
196+
if (!this.included) this.includeNode(context);
189197
this.source.include(context, includeChildrenRecursively);
198+
if (this.shouldIncludeDynamicAttributes)
199+
this.options?.include(context, includeChildrenRecursively);
190200
}
191201

192-
includeNode() {
202+
includeNode(context: InclusionContext) {
193203
this.included = true;
204+
if (this.shouldIncludeDynamicAttributes) this.options?.includePath(UNKNOWN_PATH, context);
194205
this.scope.context.includeDynamicImport(this);
195206
this.scope.addAccessedDynamicImport(this);
196207
}
197208

198-
includePath(path: ObjectPath): void {
199-
if (!this.included) this.includeNode();
209+
includePath(path: ObjectPath, context: InclusionContext): void {
210+
if (!this.included) this.includeNode(context);
200211
// Technically, this is not correct as dynamic imports return a Promise.
201212
if (this.hasUnknownAccessedKey) return;
202213
if (path[0] === UnknownKey) {

src/ast/nodes/shared/BitFlags.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ export const enum Flag {
2727
hasDeoptimizedCache = 1 << 25,
2828
hasEffects = 1 << 26,
2929
withinTopLevelAwait = 1 << 27,
30-
checkedForWarnings = 1 << 28
30+
checkedForWarnings = 1 << 28,
31+
shouldIncludeDynamicAttributes = 1 << 29
3132
}
3233

3334
export function isFlagSet(flags: number, flag: Flag): boolean {

src/ast/variables/LocalVariable.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ export default class LocalVariable extends Variable {
246246
declaration.parent.parent.callee.property.name === 'then' &&
247247
isImportExpressionNode(declaration.parent.parent.callee.object)
248248
) {
249-
declaration.parent.parent.callee.object.includePath(path);
249+
declaration.parent.parent.callee.object.includePath(path, context);
250250
}
251251
}
252252
// We need to make sure we include the correct path of the init

src/utils/logs.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -608,7 +608,7 @@ export function logImportAttributeIsInvalid(importer: string): RollupLog {
608608
code: INVALID_IMPORT_ATTRIBUTE,
609609
message: `Rollup could not statically analyze an import attribute of a dynamic import in "${relativeId(
610610
importer
611-
)}". Import attributes need to have string keys and values. The attribute will be removed.`
611+
)}". Import attributes need to have string keys and values.`
612612
};
613613
}
614614

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
module.exports = defineTest({
2+
description: 'Remove the attribute declarations for internal dynamic imports',
3+
expectedWarnings: ['INVALID_IMPORT_ATTRIBUTE'],
4+
options: {
5+
plugins: [
6+
{
7+
resolveDynamicImport() {
8+
return { id: './foo.js' };
9+
}
10+
}
11+
]
12+
},
13+
verifyAst: false
14+
});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
define(['exports'], (function (exports) { 'use strict';
2+
3+
const foo = 'foo';
4+
console.log(foo);
5+
6+
exports.foo = foo;
7+
8+
}));
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
define(['require'], (function (require) { 'use strict';
2+
3+
new Promise(function (resolve, reject) { require(['./generated-foo'], resolve, reject); });
4+
5+
}));
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
'use strict';
2+
3+
const foo = 'foo';
4+
console.log(foo);
5+
6+
exports.foo = foo;

0 commit comments

Comments
 (0)