From d683462da974a8bca56c0ec8e82ff32656fca765 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 17 Mar 2025 06:50:33 +0100 Subject: [PATCH 1/7] chore(deps): update dependency eslint-plugin-vue to v10 (#5877) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 115 ++++++++-------------------------------------- package.json | 2 +- 2 files changed, 20 insertions(+), 97 deletions(-) diff --git a/package-lock.json b/package-lock.json index db80758e8..50646f788 100644 --- a/package-lock.json +++ b/package-lock.json @@ -57,7 +57,7 @@ "eslint-config-prettier": "^10.1.1", "eslint-plugin-prettier": "^5.2.3", "eslint-plugin-unicorn": "^57.0.0", - "eslint-plugin-vue": "^9.33.0", + "eslint-plugin-vue": "^10.0.0", "fixturify": "^3.0.0", "flru": "^1.0.2", "fs-extra": "^11.3.0", @@ -7270,55 +7270,25 @@ } }, "node_modules/eslint-plugin-vue": { - "version": "9.33.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.33.0.tgz", - "integrity": "sha512-174lJKuNsuDIlLpjeXc5E2Tss8P44uIimAfGD0b90k0NoirJqpG7stLuU9Vp/9ioTOrQdWVREc4mRd1BD+CvGw==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-10.0.0.tgz", + "integrity": "sha512-XKckedtajqwmaX6u1VnECmZ6xJt+YvlmMzBPZd+/sI3ub2lpYZyFnsyWo7c3nMOQKJQudeyk1lw/JxdgeKT64w==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "globals": "^13.24.0", "natural-compare": "^1.4.0", "nth-check": "^2.1.1", "postcss-selector-parser": "^6.0.15", "semver": "^7.6.3", - "vue-eslint-parser": "^9.4.3", "xml-name-validator": "^4.0.0" }, "engines": { - "node": "^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "peerDependencies": { - "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" - } - }, - "node_modules/eslint-plugin-vue/node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint-plugin-vue/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "eslint": "^8.57.0 || ^9.0.0", + "vue-eslint-parser": "^10.0.0" } }, "node_modules/eslint-scope": { @@ -15477,76 +15447,29 @@ } }, "node_modules/vue-eslint-parser": { - "version": "9.4.3", - "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz", - "integrity": "sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-10.1.1.tgz", + "integrity": "sha512-bh2Z/Au5slro9QJ3neFYLanZtb1jH+W2bKqGHXAoYD4vZgNG3KeotL7JpPv5xzY4UXUXJl7TrIsnzECH63kd3Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "debug": "^4.3.4", - "eslint-scope": "^7.1.1", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.3.1", - "esquery": "^1.4.0", + "debug": "^4.4.0", + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.6.0", "lodash": "^4.17.21", - "semver": "^7.3.6" + "semver": "^7.6.3" }, "engines": { - "node": "^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://github.com/sponsors/mysticatea" }, "peerDependencies": { - "eslint": ">=6.0.0" - } - }, - "node_modules/vue-eslint-parser/node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/vue-eslint-parser/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/vue-eslint-parser/node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "eslint": "^8.57.0 || ^9.0.0" } }, "node_modules/vue-resize": { diff --git a/package.json b/package.json index 8e46f17e4..8ad6a9104 100644 --- a/package.json +++ b/package.json @@ -160,7 +160,7 @@ "eslint-config-prettier": "^10.1.1", "eslint-plugin-prettier": "^5.2.3", "eslint-plugin-unicorn": "^57.0.0", - "eslint-plugin-vue": "^9.33.0", + "eslint-plugin-vue": "^10.0.0", "fixturify": "^3.0.0", "flru": "^1.0.2", "fs-extra": "^11.3.0", From 2212897fd02c1fea9a9ca1c01432dffc21ab375f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Deng=20=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90?= Date: Mon, 17 Mar 2025 13:58:42 +0800 Subject: [PATCH 2/7] fix: export types (#5879) --- src/rollup/types.d.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/rollup/types.d.ts b/src/rollup/types.d.ts index 7b516959b..fba57f9e5 100644 --- a/src/rollup/types.d.ts +++ b/src/rollup/types.d.ts @@ -190,7 +190,7 @@ export type EmittedFile = EmittedAsset | EmittedChunk | EmittedPrebuiltChunk; export type EmitFile = (emittedFile: EmittedFile) => string; -interface ModuleInfo extends ModuleOptions { +export interface ModuleInfo extends ModuleOptions { ast: ProgramNode | null; code: string | null; dynamicImporters: readonly string[]; @@ -280,7 +280,7 @@ export interface ResolvedId extends ModuleOptions { export type ResolvedIdMap = Record; -interface PartialResolvedId extends Partial> { +export interface PartialResolvedId extends Partial> { external?: boolean | 'absolute' | 'relative'; id: string; resolvedBy?: string; @@ -504,7 +504,7 @@ type MakeAsync = Function_ extends ( : never; // eslint-disable-next-line @typescript-eslint/no-empty-object-type -type ObjectHook = T | ({ handler: T; order?: 'pre' | 'post' | null } & O); +export type ObjectHook = T | ({ handler: T; order?: 'pre' | 'post' | null } & O); export type PluginHooks = { [K in keyof FunctionPluginHooks]: ObjectHook< From 8c15e597042ccad1fe773eeb0986bf50baf8877f Mon Sep 17 00:00:00 2001 From: XiaoPi <530257315@qq.com> Date: Mon, 17 Mar 2025 14:15:44 +0800 Subject: [PATCH 3/7] avoiding top level await circular (#5843) * avoiding top level await circular * tweak logic * remove stack and tweak variables name * improve performance * Simplify the isFollowingTopLevelAwait logic --------- Co-authored-by: Lukas Taegert-Atkinson --- src/Module.ts | 4 + src/ast/nodes/AwaitExpression.ts | 11 ++- src/ast/nodes/ImportExpression.ts | 4 + src/ast/nodes/shared/BitFlags.ts | 3 +- src/utils/chunkAssignment.ts | 93 ++++++++++++++++--- test/chunking-form/index.js | 2 +- .../samples/top-level-await-mixed/_config.js | 4 + .../_expected/es/generated-lib-used.js | 7 ++ .../_expected/es/generated-lib-variant.js | 7 ++ .../_expected/es/generated-lib.js | 5 + .../_expected/es/main.js | 12 +++ .../_expected/system/generated-lib-used.js | 18 ++++ .../_expected/system/generated-lib-variant.js | 18 ++++ .../_expected/system/generated-lib.js | 14 +++ .../_expected/system/main.js | 21 +++++ .../samples/top-level-await-mixed/lib-used.js | 5 + .../top-level-await-mixed/lib-variant.js | 5 + .../samples/top-level-await-mixed/lib.js | 3 + .../samples/top-level-await-mixed/main.js | 10 ++ .../samples/top-level-await/_config.js | 4 + .../_expected/es/generated-lib-used.js | 7 ++ .../_expected/es/generated-lib-variant.js | 7 ++ .../_expected/es/generated-lib.js | 5 + .../top-level-await/_expected/es/main.js | 8 ++ .../_expected/system/generated-lib-used.js | 18 ++++ .../_expected/system/generated-lib-variant.js | 16 ++++ .../_expected/system/generated-lib.js | 14 +++ .../top-level-await/_expected/system/main.js | 17 ++++ .../samples/top-level-await/lib-used.js | 5 + .../samples/top-level-await/lib-variant.js | 5 + .../samples/top-level-await/lib.js | 3 + .../samples/top-level-await/main.js | 6 ++ test/types.d.ts | 4 + 33 files changed, 350 insertions(+), 15 deletions(-) create mode 100644 test/chunking-form/samples/top-level-await-mixed/_config.js create mode 100644 test/chunking-form/samples/top-level-await-mixed/_expected/es/generated-lib-used.js create mode 100644 test/chunking-form/samples/top-level-await-mixed/_expected/es/generated-lib-variant.js create mode 100644 test/chunking-form/samples/top-level-await-mixed/_expected/es/generated-lib.js create mode 100644 test/chunking-form/samples/top-level-await-mixed/_expected/es/main.js create mode 100644 test/chunking-form/samples/top-level-await-mixed/_expected/system/generated-lib-used.js create mode 100644 test/chunking-form/samples/top-level-await-mixed/_expected/system/generated-lib-variant.js create mode 100644 test/chunking-form/samples/top-level-await-mixed/_expected/system/generated-lib.js create mode 100644 test/chunking-form/samples/top-level-await-mixed/_expected/system/main.js create mode 100644 test/chunking-form/samples/top-level-await-mixed/lib-used.js create mode 100644 test/chunking-form/samples/top-level-await-mixed/lib-variant.js create mode 100644 test/chunking-form/samples/top-level-await-mixed/lib.js create mode 100644 test/chunking-form/samples/top-level-await-mixed/main.js create mode 100644 test/chunking-form/samples/top-level-await/_config.js create mode 100644 test/chunking-form/samples/top-level-await/_expected/es/generated-lib-used.js create mode 100644 test/chunking-form/samples/top-level-await/_expected/es/generated-lib-variant.js create mode 100644 test/chunking-form/samples/top-level-await/_expected/es/generated-lib.js create mode 100644 test/chunking-form/samples/top-level-await/_expected/es/main.js create mode 100644 test/chunking-form/samples/top-level-await/_expected/system/generated-lib-used.js create mode 100644 test/chunking-form/samples/top-level-await/_expected/system/generated-lib-variant.js create mode 100644 test/chunking-form/samples/top-level-await/_expected/system/generated-lib.js create mode 100644 test/chunking-form/samples/top-level-await/_expected/system/main.js create mode 100644 test/chunking-form/samples/top-level-await/lib-used.js create mode 100644 test/chunking-form/samples/top-level-await/lib-variant.js create mode 100644 test/chunking-form/samples/top-level-await/lib.js create mode 100644 test/chunking-form/samples/top-level-await/main.js diff --git a/src/Module.ts b/src/Module.ts index 9e5054eff..55298846e 100644 --- a/src/Module.ts +++ b/src/Module.ts @@ -240,6 +240,7 @@ export default class Module { shebang: undefined | string; readonly importers: string[] = []; readonly includedDynamicImporters: Module[] = []; + readonly includedDirectTopLevelAwaitingDynamicImporters = new Set(); readonly includedImports = new Set(); readonly info: ModuleInfo; isExecuted = false; @@ -1365,6 +1366,9 @@ export default class Module { if (resolution instanceof Module) { if (!resolution.includedDynamicImporters.includes(this)) { resolution.includedDynamicImporters.push(this); + if (node.isFollowingTopLevelAwait) { + resolution.includedDirectTopLevelAwaitingDynamicImporters.add(this); + } } const importedNames = this.options.treeshake diff --git a/src/ast/nodes/AwaitExpression.ts b/src/ast/nodes/AwaitExpression.ts index 912cdf02e..a0705a03f 100644 --- a/src/ast/nodes/AwaitExpression.ts +++ b/src/ast/nodes/AwaitExpression.ts @@ -2,6 +2,7 @@ import type { InclusionContext } from '../ExecutionContext'; import type { ObjectPath } from '../utils/PathTracker'; import ArrowFunctionExpression from './ArrowFunctionExpression'; import type * as NodeType from './NodeType'; +import { Flag, isFlagSet, setFlag } from './shared/BitFlags'; import FunctionNode from './shared/FunctionNode'; import { type ExpressionNode, type IncludeChildren, type Node, NodeBase } from './shared/Node'; @@ -9,6 +10,13 @@ export default class AwaitExpression extends NodeBase { declare argument: ExpressionNode; declare type: NodeType.tAwaitExpression; + get isTopLevelAwait(): boolean { + return isFlagSet(this.flags, Flag.isTopLevelAwait); + } + set isTopLevelAwait(value: boolean) { + this.flags = setFlag(this.flags, Flag.isTopLevelAwait, value); + } + hasEffects(): boolean { if (!this.deoptimized) this.applyDeoptimizations(); return true; @@ -22,13 +30,14 @@ export default class AwaitExpression extends NodeBase { includeNode(context: InclusionContext) { this.included = true; if (!this.deoptimized) this.applyDeoptimizations(); - checkTopLevelAwait: if (!this.scope.context.usesTopLevelAwait) { + checkTopLevelAwait: { let parent = this.parent; do { if (parent instanceof FunctionNode || parent instanceof ArrowFunctionExpression) break checkTopLevelAwait; } while ((parent = (parent as Node).parent as Node)); this.scope.context.usesTopLevelAwait = true; + this.isTopLevelAwait = true; } // Thenables need to be included this.argument.includePath(THEN_PATH, context); diff --git a/src/ast/nodes/ImportExpression.ts b/src/ast/nodes/ImportExpression.ts index a50cc680d..01b48d30f 100644 --- a/src/ast/nodes/ImportExpression.ts +++ b/src/ast/nodes/ImportExpression.ts @@ -58,6 +58,10 @@ export default class ImportExpression extends NodeBase { this.source.bind(); } + get isFollowingTopLevelAwait() { + return this.parent instanceof AwaitExpression && this.parent.isTopLevelAwait; + } + /** * Get imported variables for deterministic usage, valid cases are: * diff --git a/src/ast/nodes/shared/BitFlags.ts b/src/ast/nodes/shared/BitFlags.ts index 536ce091b..367573fc8 100644 --- a/src/ast/nodes/shared/BitFlags.ts +++ b/src/ast/nodes/shared/BitFlags.ts @@ -25,7 +25,8 @@ export const enum Flag { expression = 1 << 23, destructuringDeoptimized = 1 << 24, hasDeoptimizedCache = 1 << 25, - hasEffects = 1 << 26 + hasEffects = 1 << 26, + isTopLevelAwait = 1 << 27 } export function isFlagSet(flags: number, flag: Flag): boolean { diff --git a/src/utils/chunkAssignment.ts b/src/utils/chunkAssignment.ts index 13ca652c4..fda91f465 100644 --- a/src/utils/chunkAssignment.ts +++ b/src/utils/chunkAssignment.ts @@ -161,7 +161,9 @@ export function getChunkAssignments( allEntries, dependentEntriesByModule, dynamicallyDependentEntriesByDynamicEntry, - dynamicImportsByEntry + dynamicImportsByEntry, + dynamicallyDependentEntriesByAwaitedDynamicEntry, + awaitedDynamicImportsByEntry } = analyzeModuleGraph(entries); // Each chunk is identified by its position in this array @@ -177,8 +179,18 @@ export function getChunkAssignments( dynamicImportsByEntry, allEntries ); + const awaitedAlreadyLoadedAtomsByEntry = getAlreadyLoadedAtomsByEntry( + staticDependencyAtomsByEntry, + dynamicallyDependentEntriesByAwaitedDynamicEntry, + awaitedDynamicImportsByEntry, + allEntries + ); // This mutates the dependentEntries in chunkAtoms - removeUnnecessaryDependentEntries(chunkAtoms, alreadyLoadedAtomsByEntry); + removeUnnecessaryDependentEntries( + chunkAtoms, + alreadyLoadedAtomsByEntry, + awaitedAlreadyLoadedAtomsByEntry + ); const { chunks, sideEffectAtoms, sizeByAtom } = getChunksWithSameDependentEntriesAndCorrelatedAtoms( chunkAtoms, @@ -241,15 +253,21 @@ function analyzeModuleGraph(entries: Iterable): { dependentEntriesByModule: Map>; dynamicImportsByEntry: readonly ReadonlySet[]; dynamicallyDependentEntriesByDynamicEntry: Map>; + awaitedDynamicImportsByEntry: readonly ReadonlySet[]; + dynamicallyDependentEntriesByAwaitedDynamicEntry: Map>; } { const dynamicEntryModules = new Set(); + const awaitedDynamicEntryModules = new Set(); const dependentEntriesByModule = new Map>(); const allEntriesSet = new Set(entries); const dynamicImportModulesByEntry: Set[] = new Array(allEntriesSet.size); + const awaitedDynamicImportModulesByEntry: Set[] = new Array(allEntriesSet.size); let entryIndex = 0; for (const currentEntry of allEntriesSet) { const dynamicImportsForCurrentEntry = new Set(); + const awaitedDynamicImportsForCurrentEntry = new Set(); dynamicImportModulesByEntry[entryIndex] = dynamicImportsForCurrentEntry; + awaitedDynamicImportModulesByEntry[entryIndex] = awaitedDynamicImportsForCurrentEntry; const modulesToHandle = new Set([currentEntry]); for (const module of modulesToHandle) { getOrCreate(dependentEntriesByModule, module, getNewSet).add(entryIndex); @@ -267,6 +285,10 @@ function analyzeModuleGraph(entries: Iterable): { dynamicEntryModules.add(resolution); allEntriesSet.add(resolution); dynamicImportsForCurrentEntry.add(resolution); + if (resolution.includedDirectTopLevelAwaitingDynamicImporters.has(currentEntry)) { + awaitedDynamicEntryModules.add(resolution); + awaitedDynamicImportsForCurrentEntry.add(resolution); + } } } for (const dependency of module.implicitlyLoadedBefore) { @@ -279,18 +301,33 @@ function analyzeModuleGraph(entries: Iterable): { entryIndex++; } const allEntries = [...allEntriesSet]; - const { dynamicEntries, dynamicImportsByEntry } = getDynamicEntries( + const { + awaitedDynamicEntries, + awaitedDynamicImportsByEntry, + dynamicEntries, + dynamicImportsByEntry + } = getDynamicEntries( allEntries, dynamicEntryModules, - dynamicImportModulesByEntry + dynamicImportModulesByEntry, + awaitedDynamicEntryModules, + awaitedDynamicImportModulesByEntry ); return { allEntries, + awaitedDynamicImportsByEntry, dependentEntriesByModule, + dynamicallyDependentEntriesByAwaitedDynamicEntry: getDynamicallyDependentEntriesByDynamicEntry( + dependentEntriesByModule, + awaitedDynamicEntries, + allEntries, + dynamicEntry => dynamicEntry.includedDirectTopLevelAwaitingDynamicImporters + ), dynamicallyDependentEntriesByDynamicEntry: getDynamicallyDependentEntriesByDynamicEntry( dependentEntriesByModule, dynamicEntries, - allEntries + allEntries, + dynamicEntry => dynamicEntry.includedDynamicImporters ), dynamicImportsByEntry }; @@ -299,16 +336,42 @@ function analyzeModuleGraph(entries: Iterable): { function getDynamicEntries( allEntries: Module[], dynamicEntryModules: Set, - dynamicImportModulesByEntry: Set[] + dynamicImportModulesByEntry: Set[], + awaitedDynamicEntryModules: Set, + awaitedDynamicImportModulesByEntry: Set[] ) { const entryIndexByModule = new Map(); const dynamicEntries = new Set(); + const awaitedDynamicEntries = new Set(); for (const [entryIndex, entry] of allEntries.entries()) { entryIndexByModule.set(entry, entryIndex); if (dynamicEntryModules.has(entry)) { dynamicEntries.add(entryIndex); } + if (awaitedDynamicEntryModules.has(entry)) { + awaitedDynamicEntries.add(entryIndex); + } } + const dynamicImportsByEntry = getDynamicImportsByEntry( + dynamicImportModulesByEntry, + entryIndexByModule + ); + const awaitedDynamicImportsByEntry = getDynamicImportsByEntry( + awaitedDynamicImportModulesByEntry, + entryIndexByModule + ); + return { + awaitedDynamicEntries, + awaitedDynamicImportsByEntry, + dynamicEntries, + dynamicImportsByEntry + }; +} + +function getDynamicImportsByEntry( + dynamicImportModulesByEntry: Set[], + entryIndexByModule: Map +): Set[] { const dynamicImportsByEntry: Set[] = new Array(dynamicImportModulesByEntry.length); let index = 0; for (const dynamicImportModules of dynamicImportModulesByEntry) { @@ -318,13 +381,14 @@ function getDynamicEntries( } dynamicImportsByEntry[index++] = dynamicImports; } - return { dynamicEntries, dynamicImportsByEntry }; + return dynamicImportsByEntry; } function getDynamicallyDependentEntriesByDynamicEntry( dependentEntriesByModule: ReadonlyMap>, dynamicEntries: ReadonlySet, - allEntries: readonly Module[] + allEntries: readonly Module[], + getDynamicImporters: (entry: Module) => Iterable ): Map> { const dynamicallyDependentEntriesByDynamicEntry = new Map>(); for (const dynamicEntryIndex of dynamicEntries) { @@ -335,7 +399,7 @@ function getDynamicallyDependentEntriesByDynamicEntry( ); const dynamicEntry = allEntries[dynamicEntryIndex]; for (const importer of concatLazy([ - dynamicEntry.includedDynamicImporters, + getDynamicImporters(dynamicEntry), dynamicEntry.implicitlyLoadedAfter ])) { for (const entry of dependentEntriesByModule.get(importer)!) { @@ -444,14 +508,19 @@ function getAlreadyLoadedAtomsByEntry( */ function removeUnnecessaryDependentEntries( chunkAtoms: ModulesWithDependentEntries[], - alreadyLoadedAtomsByEntry: bigint[] + alreadyLoadedAtomsByEntry: bigint[], + awaitedAlreadyLoadedAtomsByEntry: bigint[] ) { // Remove entries from dependent entries if a chunk is already loaded without - // that entry. + // that entry. Do not remove already loaded atoms where all dynamic imports + // are awaited to avoid cycles in the output. let chunkMask = 1n; for (const { dependentEntries } of chunkAtoms) { for (const entryIndex of dependentEntries) { - if ((alreadyLoadedAtomsByEntry[entryIndex] & chunkMask) === chunkMask) { + if ( + (alreadyLoadedAtomsByEntry[entryIndex] & chunkMask) === chunkMask && + (awaitedAlreadyLoadedAtomsByEntry[entryIndex] & chunkMask) === 0n + ) { dependentEntries.delete(entryIndex); } } diff --git a/test/chunking-form/index.js b/test/chunking-form/index.js index bfdf9e85b..852297996 100644 --- a/test/chunking-form/index.js +++ b/test/chunking-form/index.js @@ -27,7 +27,7 @@ runTestSuiteWithSamples( const logs = []; after(() => config.logs && compareLogs(logs, config.logs)); - for (const format of FORMATS) { + for (const format of config.formats || FORMATS) { it('generates ' + format, async () => { process.chdir(directory); const warnings = []; diff --git a/test/chunking-form/samples/top-level-await-mixed/_config.js b/test/chunking-form/samples/top-level-await-mixed/_config.js new file mode 100644 index 000000000..28776a09a --- /dev/null +++ b/test/chunking-form/samples/top-level-await-mixed/_config.js @@ -0,0 +1,4 @@ +module.exports = defineTest({ + description: 'avoiding top-level await critical path for chunks', + formats: ['es', 'system'] +}); diff --git a/test/chunking-form/samples/top-level-await-mixed/_expected/es/generated-lib-used.js b/test/chunking-form/samples/top-level-await-mixed/_expected/es/generated-lib-used.js new file mode 100644 index 000000000..5753546d3 --- /dev/null +++ b/test/chunking-form/samples/top-level-await-mixed/_expected/es/generated-lib-used.js @@ -0,0 +1,7 @@ +import { g as getInfo } from './generated-lib.js'; + +function getInfoWithUsed() { + return getInfo() + '_used'; +} + +export { getInfoWithUsed }; diff --git a/test/chunking-form/samples/top-level-await-mixed/_expected/es/generated-lib-variant.js b/test/chunking-form/samples/top-level-await-mixed/_expected/es/generated-lib-variant.js new file mode 100644 index 000000000..4b074adb5 --- /dev/null +++ b/test/chunking-form/samples/top-level-await-mixed/_expected/es/generated-lib-variant.js @@ -0,0 +1,7 @@ +import { g as getInfo } from './generated-lib.js'; + +function getInfoWithVariant() { + return getInfo() + '_variant'; +} + +export { getInfoWithVariant }; diff --git a/test/chunking-form/samples/top-level-await-mixed/_expected/es/generated-lib.js b/test/chunking-form/samples/top-level-await-mixed/_expected/es/generated-lib.js new file mode 100644 index 000000000..b9c6222cc --- /dev/null +++ b/test/chunking-form/samples/top-level-await-mixed/_expected/es/generated-lib.js @@ -0,0 +1,5 @@ +function getInfo() { + return 'info'; +} + +export { getInfo as g }; diff --git a/test/chunking-form/samples/top-level-await-mixed/_expected/es/main.js b/test/chunking-form/samples/top-level-await-mixed/_expected/es/main.js new file mode 100644 index 000000000..b5950df59 --- /dev/null +++ b/test/chunking-form/samples/top-level-await-mixed/_expected/es/main.js @@ -0,0 +1,12 @@ +import { g as getInfo } from './generated-lib.js'; + +let getCommonInfo = getInfo; + +import('./generated-lib-used.js').then(({ getInfoWithUsed }) => { + getCommonInfo = getInfoWithUsed; +}); + +const { getInfoWithVariant } = await import('./generated-lib-variant.js'); +getCommonInfo = getInfoWithVariant; + +export { getCommonInfo }; diff --git a/test/chunking-form/samples/top-level-await-mixed/_expected/system/generated-lib-used.js b/test/chunking-form/samples/top-level-await-mixed/_expected/system/generated-lib-used.js new file mode 100644 index 000000000..99e5e7248 --- /dev/null +++ b/test/chunking-form/samples/top-level-await-mixed/_expected/system/generated-lib-used.js @@ -0,0 +1,18 @@ +System.register(['./generated-lib.js'], (function (exports) { + 'use strict'; + var getInfo; + return { + setters: [function (module) { + getInfo = module.g; + }], + execute: (function () { + + exports("getInfoWithUsed", getInfoWithUsed); + + function getInfoWithUsed() { + return getInfo() + '_used'; + } + + }) + }; +})); diff --git a/test/chunking-form/samples/top-level-await-mixed/_expected/system/generated-lib-variant.js b/test/chunking-form/samples/top-level-await-mixed/_expected/system/generated-lib-variant.js new file mode 100644 index 000000000..69075b561 --- /dev/null +++ b/test/chunking-form/samples/top-level-await-mixed/_expected/system/generated-lib-variant.js @@ -0,0 +1,18 @@ +System.register(['./generated-lib.js'], (function (exports) { + 'use strict'; + var getInfo; + return { + setters: [function (module) { + getInfo = module.g; + }], + execute: (function () { + + exports("getInfoWithVariant", getInfoWithVariant); + + function getInfoWithVariant() { + return getInfo() + '_variant'; + } + + }) + }; +})); diff --git a/test/chunking-form/samples/top-level-await-mixed/_expected/system/generated-lib.js b/test/chunking-form/samples/top-level-await-mixed/_expected/system/generated-lib.js new file mode 100644 index 000000000..59ed1f2a0 --- /dev/null +++ b/test/chunking-form/samples/top-level-await-mixed/_expected/system/generated-lib.js @@ -0,0 +1,14 @@ +System.register([], (function (exports) { + 'use strict'; + return { + execute: (function () { + + exports("g", getInfo); + + function getInfo() { + return 'info'; + } + + }) + }; +})); diff --git a/test/chunking-form/samples/top-level-await-mixed/_expected/system/main.js b/test/chunking-form/samples/top-level-await-mixed/_expected/system/main.js new file mode 100644 index 000000000..8a708ede5 --- /dev/null +++ b/test/chunking-form/samples/top-level-await-mixed/_expected/system/main.js @@ -0,0 +1,21 @@ +System.register(['./generated-lib.js'], (function (exports, module) { + 'use strict'; + var getInfo; + return { + setters: [function (module) { + getInfo = module.g; + }], + execute: (async function () { + + let getCommonInfo = exports("getCommonInfo", getInfo); + + module.import('./generated-lib-used.js').then(({ getInfoWithUsed }) => { + exports("getCommonInfo", getCommonInfo = getInfoWithUsed); + }); + + const { getInfoWithVariant } = await module.import('./generated-lib-variant.js'); + exports("getCommonInfo", getCommonInfo = getInfoWithVariant); + + }) + }; +})); diff --git a/test/chunking-form/samples/top-level-await-mixed/lib-used.js b/test/chunking-form/samples/top-level-await-mixed/lib-used.js new file mode 100644 index 000000000..df58e52a5 --- /dev/null +++ b/test/chunking-form/samples/top-level-await-mixed/lib-used.js @@ -0,0 +1,5 @@ +import { getInfo } from './lib.js'; + +export function getInfoWithUsed() { + return getInfo() + '_used'; +} diff --git a/test/chunking-form/samples/top-level-await-mixed/lib-variant.js b/test/chunking-form/samples/top-level-await-mixed/lib-variant.js new file mode 100644 index 000000000..57b99a9e1 --- /dev/null +++ b/test/chunking-form/samples/top-level-await-mixed/lib-variant.js @@ -0,0 +1,5 @@ +import { getInfo } from './lib.js'; + +export function getInfoWithVariant() { + return getInfo() + '_variant'; +} diff --git a/test/chunking-form/samples/top-level-await-mixed/lib.js b/test/chunking-form/samples/top-level-await-mixed/lib.js new file mode 100644 index 000000000..60807167e --- /dev/null +++ b/test/chunking-form/samples/top-level-await-mixed/lib.js @@ -0,0 +1,3 @@ +export function getInfo() { + return 'info'; +} diff --git a/test/chunking-form/samples/top-level-await-mixed/main.js b/test/chunking-form/samples/top-level-await-mixed/main.js new file mode 100644 index 000000000..ecae9de7b --- /dev/null +++ b/test/chunking-form/samples/top-level-await-mixed/main.js @@ -0,0 +1,10 @@ +import { getInfo } from './lib.js'; + +export let getCommonInfo = getInfo; + +import('./lib-used.js').then(({ getInfoWithUsed }) => { + getCommonInfo = getInfoWithUsed; +}); + +const { getInfoWithVariant } = await import('./lib-variant.js'); +getCommonInfo = getInfoWithVariant; diff --git a/test/chunking-form/samples/top-level-await/_config.js b/test/chunking-form/samples/top-level-await/_config.js new file mode 100644 index 000000000..28776a09a --- /dev/null +++ b/test/chunking-form/samples/top-level-await/_config.js @@ -0,0 +1,4 @@ +module.exports = defineTest({ + description: 'avoiding top-level await critical path for chunks', + formats: ['es', 'system'] +}); diff --git a/test/chunking-form/samples/top-level-await/_expected/es/generated-lib-used.js b/test/chunking-form/samples/top-level-await/_expected/es/generated-lib-used.js new file mode 100644 index 000000000..5753546d3 --- /dev/null +++ b/test/chunking-form/samples/top-level-await/_expected/es/generated-lib-used.js @@ -0,0 +1,7 @@ +import { g as getInfo } from './generated-lib.js'; + +function getInfoWithUsed() { + return getInfo() + '_used'; +} + +export { getInfoWithUsed }; diff --git a/test/chunking-form/samples/top-level-await/_expected/es/generated-lib-variant.js b/test/chunking-form/samples/top-level-await/_expected/es/generated-lib-variant.js new file mode 100644 index 000000000..2e24ab2e1 --- /dev/null +++ b/test/chunking-form/samples/top-level-await/_expected/es/generated-lib-variant.js @@ -0,0 +1,7 @@ +const { getInfoWithUsed } = await import('./generated-lib-used.js'); + +function getInfoWithVariant() { + return getInfoWithUsed() + '_variant'; +} + +export { getInfoWithVariant }; diff --git a/test/chunking-form/samples/top-level-await/_expected/es/generated-lib.js b/test/chunking-form/samples/top-level-await/_expected/es/generated-lib.js new file mode 100644 index 000000000..b9c6222cc --- /dev/null +++ b/test/chunking-form/samples/top-level-await/_expected/es/generated-lib.js @@ -0,0 +1,5 @@ +function getInfo() { + return 'info'; +} + +export { getInfo as g }; diff --git a/test/chunking-form/samples/top-level-await/_expected/es/main.js b/test/chunking-form/samples/top-level-await/_expected/es/main.js new file mode 100644 index 000000000..78c21f639 --- /dev/null +++ b/test/chunking-form/samples/top-level-await/_expected/es/main.js @@ -0,0 +1,8 @@ +import { g as getInfo } from './generated-lib.js'; + +let getCommonInfo = getInfo; + +const { getInfoWithVariant } = await import('./generated-lib-variant.js'); +getCommonInfo = getInfoWithVariant; + +export { getCommonInfo }; diff --git a/test/chunking-form/samples/top-level-await/_expected/system/generated-lib-used.js b/test/chunking-form/samples/top-level-await/_expected/system/generated-lib-used.js new file mode 100644 index 000000000..99e5e7248 --- /dev/null +++ b/test/chunking-form/samples/top-level-await/_expected/system/generated-lib-used.js @@ -0,0 +1,18 @@ +System.register(['./generated-lib.js'], (function (exports) { + 'use strict'; + var getInfo; + return { + setters: [function (module) { + getInfo = module.g; + }], + execute: (function () { + + exports("getInfoWithUsed", getInfoWithUsed); + + function getInfoWithUsed() { + return getInfo() + '_used'; + } + + }) + }; +})); diff --git a/test/chunking-form/samples/top-level-await/_expected/system/generated-lib-variant.js b/test/chunking-form/samples/top-level-await/_expected/system/generated-lib-variant.js new file mode 100644 index 000000000..3220c6417 --- /dev/null +++ b/test/chunking-form/samples/top-level-await/_expected/system/generated-lib-variant.js @@ -0,0 +1,16 @@ +System.register([], (function (exports, module) { + 'use strict'; + return { + execute: (async function () { + + exports("getInfoWithVariant", getInfoWithVariant); + + const { getInfoWithUsed } = await module.import('./generated-lib-used.js'); + + function getInfoWithVariant() { + return getInfoWithUsed() + '_variant'; + } + + }) + }; +})); diff --git a/test/chunking-form/samples/top-level-await/_expected/system/generated-lib.js b/test/chunking-form/samples/top-level-await/_expected/system/generated-lib.js new file mode 100644 index 000000000..59ed1f2a0 --- /dev/null +++ b/test/chunking-form/samples/top-level-await/_expected/system/generated-lib.js @@ -0,0 +1,14 @@ +System.register([], (function (exports) { + 'use strict'; + return { + execute: (function () { + + exports("g", getInfo); + + function getInfo() { + return 'info'; + } + + }) + }; +})); diff --git a/test/chunking-form/samples/top-level-await/_expected/system/main.js b/test/chunking-form/samples/top-level-await/_expected/system/main.js new file mode 100644 index 000000000..cd8a7ef99 --- /dev/null +++ b/test/chunking-form/samples/top-level-await/_expected/system/main.js @@ -0,0 +1,17 @@ +System.register(['./generated-lib.js'], (function (exports, module) { + 'use strict'; + var getInfo; + return { + setters: [function (module) { + getInfo = module.g; + }], + execute: (async function () { + + let getCommonInfo = exports("getCommonInfo", getInfo); + + const { getInfoWithVariant } = await module.import('./generated-lib-variant.js'); + exports("getCommonInfo", getCommonInfo = getInfoWithVariant); + + }) + }; +})); diff --git a/test/chunking-form/samples/top-level-await/lib-used.js b/test/chunking-form/samples/top-level-await/lib-used.js new file mode 100644 index 000000000..df58e52a5 --- /dev/null +++ b/test/chunking-form/samples/top-level-await/lib-used.js @@ -0,0 +1,5 @@ +import { getInfo } from './lib.js'; + +export function getInfoWithUsed() { + return getInfo() + '_used'; +} diff --git a/test/chunking-form/samples/top-level-await/lib-variant.js b/test/chunking-form/samples/top-level-await/lib-variant.js new file mode 100644 index 000000000..b2aa14578 --- /dev/null +++ b/test/chunking-form/samples/top-level-await/lib-variant.js @@ -0,0 +1,5 @@ +const { getInfoWithUsed } = await import('./lib-used.js'); + +export function getInfoWithVariant() { + return getInfoWithUsed() + '_variant'; +} diff --git a/test/chunking-form/samples/top-level-await/lib.js b/test/chunking-form/samples/top-level-await/lib.js new file mode 100644 index 000000000..60807167e --- /dev/null +++ b/test/chunking-form/samples/top-level-await/lib.js @@ -0,0 +1,3 @@ +export function getInfo() { + return 'info'; +} diff --git a/test/chunking-form/samples/top-level-await/main.js b/test/chunking-form/samples/top-level-await/main.js new file mode 100644 index 000000000..ab81b8ed7 --- /dev/null +++ b/test/chunking-form/samples/top-level-await/main.js @@ -0,0 +1,6 @@ +import { getInfo } from './lib.js'; + +export let getCommonInfo = getInfo; + +const { getInfoWithVariant } = await import('./lib-variant.js'); +getCommonInfo = getInfoWithVariant; diff --git a/test/types.d.ts b/test/types.d.ts index ac6a61db5..bdff63bd3 100644 --- a/test/types.d.ts +++ b/test/types.d.ts @@ -63,6 +63,10 @@ export interface TestConfigChunkingForm extends TestConfigBase { * fail. */ expectedWarnings?: string[]; + /** + * Output formats. + */ + formats?: string[]; /** * Assert the expected logs. */ From f687af5c0d0fc771a2b0cc1fae9c5ce34fad071e Mon Sep 17 00:00:00 2001 From: vadym-khodak <85458173+vadym-khodak@users.noreply.github.com> Date: Mon, 17 Mar 2025 08:36:54 +0200 Subject: [PATCH 4/7] Update axios overrides to 1.8.2 (#5876) * Update axios overrides to 1.8.2 * update package-lock.json * update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8ad6a9104..d954847dd 100644 --- a/package.json +++ b/package.json @@ -203,7 +203,7 @@ "yargs-parser": "^21.1.1" }, "overrides": { - "axios": "^1.8.1", + "axios": "^1.8.3", "semver": "^7.7.1", "readable-stream": "npm:@built-in/readable-stream@1", "esbuild": ">0.24.2" From 7f0164ea77421954f84f4d27832c2e5de9c24149 Mon Sep 17 00:00:00 2001 From: iczero Date: Sun, 16 Mar 2025 23:41:05 -0700 Subject: [PATCH 5/7] draft for extended renderDynamicImport hook (#5870) * hoist transitive imports before rendering chunks * add transitive import information to renderDynamicImport * fix test coverage * capitalization consistency, move function to outer scope * very minor cleanup * fix field name, add chunk filenames to test * add documentation, fix capitalization --------- Co-authored-by: Lukas Taegert-Atkinson --- docs/plugin-development/index.md | 73 ++++++++++++++++++- src/Bundle.ts | 1 + src/Chunk.ts | 36 +++++---- src/ast/nodes/ImportExpression.ts | 51 ++++++++++++- src/rollup/types.d.ts | 20 +++++ .../render-dynamic-import-extended/_config.js | 26 +++++++ .../_expected/amd/generated-chain-2.js | 9 +++ .../_expected/amd/generated-chain-3.js | 7 ++ .../amd/generated-imports-external.js | 26 +++++++ .../_expected/amd/generated-leaf.js | 7 ++ .../_expected/amd/main.js | 9 +++ .../_expected/cjs/generated-chain-2.js | 9 +++ .../_expected/cjs/generated-chain-3.js | 8 ++ .../cjs/generated-imports-external.js | 26 +++++++ .../_expected/cjs/generated-leaf.js | 5 ++ .../_expected/cjs/main.js | 7 ++ .../_expected/es/generated-chain-2.js | 6 ++ .../_expected/es/generated-chain-3.js | 6 ++ .../es/generated-imports-external.js | 2 + .../_expected/es/generated-leaf.js | 3 + .../_expected/es/main.js | 5 ++ .../_expected/system/generated-chain-2.js | 15 ++++ .../_expected/system/generated-chain-3.js | 16 ++++ .../system/generated-imports-external.js | 13 ++++ .../_expected/system/generated-leaf.js | 10 +++ .../_expected/system/main.js | 14 ++++ .../render-dynamic-import-extended/chain-2.js | 4 + .../render-dynamic-import-extended/chain-3.js | 3 + .../imports-external.js | 1 + .../render-dynamic-import-extended/leaf.js | 1 + .../render-dynamic-import-extended/main.js | 5 ++ 31 files changed, 405 insertions(+), 19 deletions(-) create mode 100644 test/chunking-form/samples/render-dynamic-import-extended/_config.js create mode 100644 test/chunking-form/samples/render-dynamic-import-extended/_expected/amd/generated-chain-2.js create mode 100644 test/chunking-form/samples/render-dynamic-import-extended/_expected/amd/generated-chain-3.js create mode 100644 test/chunking-form/samples/render-dynamic-import-extended/_expected/amd/generated-imports-external.js create mode 100644 test/chunking-form/samples/render-dynamic-import-extended/_expected/amd/generated-leaf.js create mode 100644 test/chunking-form/samples/render-dynamic-import-extended/_expected/amd/main.js create mode 100644 test/chunking-form/samples/render-dynamic-import-extended/_expected/cjs/generated-chain-2.js create mode 100644 test/chunking-form/samples/render-dynamic-import-extended/_expected/cjs/generated-chain-3.js create mode 100644 test/chunking-form/samples/render-dynamic-import-extended/_expected/cjs/generated-imports-external.js create mode 100644 test/chunking-form/samples/render-dynamic-import-extended/_expected/cjs/generated-leaf.js create mode 100644 test/chunking-form/samples/render-dynamic-import-extended/_expected/cjs/main.js create mode 100644 test/chunking-form/samples/render-dynamic-import-extended/_expected/es/generated-chain-2.js create mode 100644 test/chunking-form/samples/render-dynamic-import-extended/_expected/es/generated-chain-3.js create mode 100644 test/chunking-form/samples/render-dynamic-import-extended/_expected/es/generated-imports-external.js create mode 100644 test/chunking-form/samples/render-dynamic-import-extended/_expected/es/generated-leaf.js create mode 100644 test/chunking-form/samples/render-dynamic-import-extended/_expected/es/main.js create mode 100644 test/chunking-form/samples/render-dynamic-import-extended/_expected/system/generated-chain-2.js create mode 100644 test/chunking-form/samples/render-dynamic-import-extended/_expected/system/generated-chain-3.js create mode 100644 test/chunking-form/samples/render-dynamic-import-extended/_expected/system/generated-imports-external.js create mode 100644 test/chunking-form/samples/render-dynamic-import-extended/_expected/system/generated-leaf.js create mode 100644 test/chunking-form/samples/render-dynamic-import-extended/_expected/system/main.js create mode 100644 test/chunking-form/samples/render-dynamic-import-extended/chain-2.js create mode 100644 test/chunking-form/samples/render-dynamic-import-extended/chain-3.js create mode 100644 test/chunking-form/samples/render-dynamic-import-extended/imports-external.js create mode 100644 test/chunking-form/samples/render-dynamic-import-extended/leaf.js create mode 100644 test/chunking-form/samples/render-dynamic-import-extended/main.js diff --git a/docs/plugin-development/index.md b/docs/plugin-development/index.md index 83b367479..3b96ba785 100644 --- a/docs/plugin-development/index.md +++ b/docs/plugin-development/index.md @@ -1059,12 +1059,38 @@ type renderDynamicImportHook = (options: { format: string; moduleId: string; targetModuleId: string | null; + chunk: PreRenderedChunkWithFileName; + targetChunk: PreRenderedChunkWithFileName; + getTargetChunkImports: () => DynamicImportTargetChunk[] | null; }) => { left: string; right: string } | null; + +type PreRenderedChunkWithFileName = PreRenderedChunk & { fileName: string }; + +type DynamicImportTargetChunk = + | ImportedInternalChunk + | ImportedExternalChunk; + +interface ImportedInternalChunk { + type: 'internal'; + fileName: string; + resolvedImportPath: string; + chunk: PreRenderedChunk; +} + +interface ImportedExternalChunk { + type: 'external'; + fileName: string; + resolvedImportPath: string; +} ``` +See the [`chunkFileNames`](../configuration-options/index.md#output-chunkfilenames) option for the `PreRenderedChunk` type. + This hook provides fine-grained control over how dynamic imports are rendered by providing replacements for the code to the left (`import(`) and right (`)`) of the argument of the import expression. Returning `null` defers to other hooks of this type and ultimately renders a format-specific default. -`format` is the rendered output format, `moduleId` the id of the module performing the dynamic import. If the import could be resolved to an internal or external id, then `targetModuleId` will be set to this id, otherwise it will be `null`. If the dynamic import contained a non-string expression that was resolved by a [`resolveDynamicImport`](#resolvedynamicimport) hook to a replacement string, then `customResolution` will contain that string. +`format` is the rendered output format, `moduleId` the id of the module performing the dynamic import. If the import could be resolved to an internal or external id, then `targetModuleId` will be set to this id, otherwise it will be `null`. If the dynamic import contained a non-string expression that was resolved by a [`resolveDynamicImport`](#resolvedynamicimport) hook to a replacement string, then `customResolution` will contain that string. `chunk` and `targetChunk` provide additional information about the chunk performing the import and the chunk being imported (the target chunk), respectively. `getTargetChunkImports` returns an array containing chunks which are imported by the target chunk. If the target chunk is unresolved or external, `targetChunk` will be null and `getTargetChunkImports` will return null. + +The `PreRenderedChunkWithFileName` type is identical to the `PreRenderedChunk` type except for the addition of the `fileName` field, which contains the path and file name of the chunk. `fileName` may contain a placeholder if the chunk file name format contains a hash. The following code will replace all dynamic imports with a custom handler, adding `import.meta.url` as a second argument to allow the handler to resolve relative imports correctly: @@ -1116,6 +1142,51 @@ function retainImportExpressionPlugin() { } ``` +When a dynamic import occurs, the browser will fetch the requested module and parse it. If the target module is discovered to have imports and they have not already been fetched, the browser must perform more network requests before it can execute the module. This will incur the latency of an additional network round trip. For static modules, Rollup will hoist transitive dependencies ([`hoistTransitiveDependencies`](../configuration-options/index.md#output-hoisttransitiveimports)) to prevent this from occuring. However, dependency hoisting is currently not automatically performed for dynamic imports. + +The following plugin can achieve similar preloading behavior for dynamic imports: + +```js twoslash +// ---cut-start--- +/** @returns {import('rollup').Plugin} */ +// ---cut-end--- +function dynamicImportDependencyPreloader() { + return { + name: 'dynamic-import-dependency-preloader', + renderDynamicImport({ getTargetChunkImports }) { + const transitiveImports = getTargetChunkImports(); + if (transitiveImports && transitiveImports.length > 0) { + const preload = getTargetChunkImports() + .map( + chunk => `\t/* preload */ import(${chunk.resolvedImportPath})` + ) + .join(',\n'); + return { + left: `(\n${preload},\n\timport(`, + right: `)\n)` + }; + } else { + return null; + } + } + }; +} +``` + + +```js +// input +import('./lib.js'); + +// output +( + /* preload */ import('./dependency-1.js'), + /* preload */ import('./dependency-2.js'), + import('./lib.js'); +); +``` + + Note that when this hook rewrites dynamic imports in non-ES formats, no interop code to make sure that e.g. the default export is available as `.default` is generated. It is the responsibility of the plugin to make sure the rewritten dynamic import returns a Promise that resolves to a proper namespace object. ### renderError diff --git a/src/Bundle.ts b/src/Bundle.ts index a1cc9e99e..760ac6a1f 100644 --- a/src/Bundle.ts +++ b/src/Bundle.ts @@ -71,6 +71,7 @@ export default class Bundle { this.pluginDriver.setChunkInformation(this.facadeChunkByModule); for (const chunk of chunks) { chunk.generateExports(); + chunk.inlineTransitiveImports(); } timeEnd('generate chunks', 2); diff --git a/src/Chunk.ts b/src/Chunk.ts index 46e35bd25..f4995f39c 100644 --- a/src/Chunk.ts +++ b/src/Chunk.ts @@ -165,6 +165,7 @@ function getGlobalName( export default class Chunk { // placeholder declaration, only relevant for ExternalChunk defaultVariableName?: undefined; + dependencies = new Set(); readonly entryModules: Module[] = []; execIndex: number; exportMode: 'none' | 'named' | 'default' = 'named'; @@ -174,7 +175,6 @@ export default class Chunk { variableName = ''; private readonly accessedGlobalsByScope = new Map>(); - private dependencies = new Set(); private readonly dynamicEntryModules: Module[] = []; private dynamicName: string | null = null; private readonly exportNamesByVariable = new Map(); @@ -622,9 +622,21 @@ export default class Chunk { } } + inlineTransitiveImports(): void { + const { facadeModule, dependencies, outputOptions } = this; + const { hoistTransitiveImports, preserveModules } = outputOptions; + + // for static and dynamic entry points, add transitive dependencies to this + // chunk's dependencies to avoid loading latency + if (hoistTransitiveImports && !preserveModules && facadeModule !== null) { + for (const dep of dependencies) { + if (dep instanceof Chunk) this.inlineChunkDependencies(dep); + } + } + } + async render(): Promise { const { - dependencies, exportMode, facadeModule, inputOptions: { onLog }, @@ -632,15 +644,7 @@ export default class Chunk { pluginDriver, snippets } = this; - const { format, hoistTransitiveImports, preserveModules } = outputOptions; - - // for static and dynamic entry points, add transitive dependencies to this - // chunk's dependencies to avoid loading latency - if (hoistTransitiveImports && !preserveModules && facadeModule !== null) { - for (const dep of dependencies) { - if (dep instanceof Chunk) this.inlineChunkDependencies(dep); - } - } + const { format, preserveModules } = outputOptions; const preliminaryFileName = this.getPreliminaryFileName(); const preliminarySourcemapFileName = this.getPreliminarySourcemapFileName(); @@ -1053,7 +1057,7 @@ export default class Chunk { return (this.includedDynamicImports = includedDynamicImports); } - private getPreRenderedChunkInfo(): PreRenderedChunk { + getPreRenderedChunkInfo(): PreRenderedChunk { if (this.preRenderedChunkInfo) { return this.preRenderedChunkInfo; } @@ -1330,7 +1334,9 @@ export default class Chunk { accessedGlobalsByScope, `'${(facadeChunk || chunk).getImportPath(fileName)}'`, !facadeChunk?.strictFacade && chunk.exportNamesByVariable.get(resolution.namespace)![0], - null + null, + this, + facadeChunk || chunk ); } } else { @@ -1349,7 +1355,9 @@ export default class Chunk { accessedGlobalsByScope, resolutionString, false, - attributes + attributes, + this, + null ); } } diff --git a/src/ast/nodes/ImportExpression.ts b/src/ast/nodes/ImportExpression.ts index 01b48d30f..3f9e9dfcf 100644 --- a/src/ast/nodes/ImportExpression.ts +++ b/src/ast/nodes/ImportExpression.ts @@ -1,7 +1,15 @@ import type MagicString from 'magic-string'; +import type Chunk from '../../Chunk'; +import ExternalChunk from '../../ExternalChunk'; import ExternalModule from '../../ExternalModule'; import type Module from '../../Module'; -import type { AstNode, GetInterop, NormalizedOutputOptions } from '../../rollup/types'; +import type { + AstNode, + DynamicImportTargetChunk, + GetInterop, + NormalizedOutputOptions, + PreRenderedChunkWithFileName +} from '../../rollup/types'; import { EMPTY_ARRAY } from '../../utils/blank'; import type { GenerateCodeSnippets } from '../../utils/generateCodeSnippets'; import { @@ -38,6 +46,10 @@ interface DynamicImportMechanism { right: string; } +function getChunkInfoWithPath(chunk: Chunk): PreRenderedChunkWithFileName { + return { fileName: chunk.getFileName(), ...chunk.getPreRenderedChunkInfo() }; +} + export default class ImportExpression extends NodeBase { declare options: ExpressionNode | null; inlineNamespace: NamespaceVariable | null = null; @@ -260,7 +272,9 @@ export default class ImportExpression extends NodeBase { accessedGlobalsByScope: Map>, resolutionString: string, namespaceExportName: string | false | undefined, - attributes: string | null | true + attributes: string | null | true, + ownChunk: Chunk, + targetChunk: Chunk | null ): void { const { format } = options; this.inlineNamespace = null; @@ -275,7 +289,9 @@ export default class ImportExpression extends NodeBase { exportMode, options, snippets, - pluginDriver + pluginDriver, + ownChunk, + targetChunk )); if (helper) { accessedGlobals.push(helper); @@ -300,13 +316,40 @@ export default class ImportExpression extends NodeBase { interop }: NormalizedOutputOptions, { _, getDirectReturnFunction, getDirectReturnIifeLeft }: GenerateCodeSnippets, - pluginDriver: PluginDriver + pluginDriver: PluginDriver, + ownChunk: Chunk, + targetChunk: Chunk | null ): { helper: string | null; mechanism: DynamicImportMechanism | null } { const mechanism = pluginDriver.hookFirstSync('renderDynamicImport', [ { + chunk: getChunkInfoWithPath(ownChunk), customResolution: typeof this.resolution === 'string' ? this.resolution : null, format, + getTargetChunkImports(): DynamicImportTargetChunk[] | null { + if (targetChunk === null) return null; + const chunkInfos: DynamicImportTargetChunk[] = []; + const importerPath = ownChunk.getFileName(); + for (const dep of targetChunk.dependencies) { + const resolvedImportPath = `'${dep.getImportPath(importerPath)}'`; + if (dep instanceof ExternalChunk) { + chunkInfos.push({ + fileName: dep.getFileName(), + resolvedImportPath, + type: 'external' + }); + } else { + chunkInfos.push({ + chunk: dep.getPreRenderedChunkInfo(), + fileName: dep.getFileName(), + resolvedImportPath, + type: 'internal' + }); + } + } + return chunkInfos; + }, moduleId: this.scope.context.module.id, + targetChunk: targetChunk ? getChunkInfoWithPath(targetChunk) : null, targetModuleId: this.resolution && typeof this.resolution !== 'string' ? this.resolution.id : null } diff --git a/src/rollup/types.d.ts b/src/rollup/types.d.ts index fba57f9e5..ca6e9fe99 100644 --- a/src/rollup/types.d.ts +++ b/src/rollup/types.d.ts @@ -400,6 +400,23 @@ export type PluginImpl = (options?: O) => Pl export type OutputBundle = Record; +export type PreRenderedChunkWithFileName = PreRenderedChunk & { fileName: string }; + +export interface ImportedInternalChunk { + type: 'internal'; + fileName: string; + resolvedImportPath: string; + chunk: PreRenderedChunk; +} + +export interface ImportedExternalChunk { + type: 'external'; + fileName: string; + resolvedImportPath: string; +} + +export type DynamicImportTargetChunk = ImportedInternalChunk | ImportedExternalChunk; + export interface FunctionPluginHooks { augmentChunkHash: (this: PluginContext, chunk: RenderedChunk) => string | void; buildEnd: (this: PluginContext, error?: Error) => void; @@ -425,6 +442,9 @@ export interface FunctionPluginHooks { format: InternalModuleFormat; moduleId: string; targetModuleId: string | null; + chunk: PreRenderedChunkWithFileName; + targetChunk: PreRenderedChunkWithFileName | null; + getTargetChunkImports: () => DynamicImportTargetChunk[] | null; } ) => { left: string; right: string } | NullValue; renderError: (this: PluginContext, error?: Error) => void; diff --git a/test/chunking-form/samples/render-dynamic-import-extended/_config.js b/test/chunking-form/samples/render-dynamic-import-extended/_config.js new file mode 100644 index 000000000..b90f38c62 --- /dev/null +++ b/test/chunking-form/samples/render-dynamic-import-extended/_config.js @@ -0,0 +1,26 @@ +module.exports = defineTest({ + description: 'supports extended custom rendering for dynamic imports', + options: { + output: { + manualChunks(id) { + if (id.includes('leaf')) return 'leaf'; + } + }, + external: ['external-module'], + plugins: { + name: 'test-plugin', + renderDynamicImport({ format, chunk, targetChunk, getTargetChunkImports }) { + const transitiveImports = getTargetChunkImports(); + const resolvedImports = transitiveImports + ? Object.fromEntries( + getTargetChunkImports().map(chunk => [chunk.fileName, chunk.resolvedImportPath]) + ) + : null; + return { + left: `${format}DynamicImportPreload(`, + right: `, ${JSON.stringify(resolvedImports)}, ${JSON.stringify(chunk?.fileName ?? null)}, ${JSON.stringify(targetChunk?.fileName ?? null)})` + }; + } + } + } +}); diff --git a/test/chunking-form/samples/render-dynamic-import-extended/_expected/amd/generated-chain-2.js b/test/chunking-form/samples/render-dynamic-import-extended/_expected/amd/generated-chain-2.js new file mode 100644 index 000000000..43019de85 --- /dev/null +++ b/test/chunking-form/samples/render-dynamic-import-extended/_expected/amd/generated-chain-2.js @@ -0,0 +1,9 @@ +define(['exports', './generated-leaf'], (function (exports, leaf) { 'use strict'; + + const four = leaf.three + 1; + var fortyTwo = 42; + + exports.default = fortyTwo; + exports.four = four; + +})); diff --git a/test/chunking-form/samples/render-dynamic-import-extended/_expected/amd/generated-chain-3.js b/test/chunking-form/samples/render-dynamic-import-extended/_expected/amd/generated-chain-3.js new file mode 100644 index 000000000..5e23ad0ac --- /dev/null +++ b/test/chunking-form/samples/render-dynamic-import-extended/_expected/amd/generated-chain-3.js @@ -0,0 +1,7 @@ +define(['exports', './generated-chain-2', './generated-leaf'], (function (exports, chain2, leaf) { 'use strict'; + + console.log('from import:', chain2.default, chain2.four); + + exports.default = chain2.default; + +})); diff --git a/test/chunking-form/samples/render-dynamic-import-extended/_expected/amd/generated-imports-external.js b/test/chunking-form/samples/render-dynamic-import-extended/_expected/amd/generated-imports-external.js new file mode 100644 index 000000000..69bcbba10 --- /dev/null +++ b/test/chunking-form/samples/render-dynamic-import-extended/_expected/amd/generated-imports-external.js @@ -0,0 +1,26 @@ +define(['exports', 'external-module'], (function (exports, externalModule) { 'use strict'; + + function _interopNamespaceDefault(e) { + var n = Object.create(null); + if (e) { + Object.keys(e).forEach(function (k) { + if (k !== 'default') { + var d = Object.getOwnPropertyDescriptor(e, k); + Object.defineProperty(n, k, d.get ? d : { + enumerable: true, + get: function () { return e[k]; } + }); + } + }); + } + n.default = e; + return Object.freeze(n); + } + + var externalModule__namespace = /*#__PURE__*/_interopNamespaceDefault(externalModule); + + + + exports.fromExternal = externalModule__namespace; + +})); diff --git a/test/chunking-form/samples/render-dynamic-import-extended/_expected/amd/generated-leaf.js b/test/chunking-form/samples/render-dynamic-import-extended/_expected/amd/generated-leaf.js new file mode 100644 index 000000000..9d890746f --- /dev/null +++ b/test/chunking-form/samples/render-dynamic-import-extended/_expected/amd/generated-leaf.js @@ -0,0 +1,7 @@ +define(['exports'], (function (exports) { 'use strict'; + + var three = 3; + + exports.three = three; + +})); diff --git a/test/chunking-form/samples/render-dynamic-import-extended/_expected/amd/main.js b/test/chunking-form/samples/render-dynamic-import-extended/_expected/amd/main.js new file mode 100644 index 000000000..bd6445cbd --- /dev/null +++ b/test/chunking-form/samples/render-dynamic-import-extended/_expected/amd/main.js @@ -0,0 +1,9 @@ +define(['require'], (function (require) { 'use strict'; + + amdDynamicImportPreload('./generated-chain-3', {"generated-chain-2.js":"'./generated-chain-2'","generated-leaf.js":"'./generated-leaf'"}, "main.js", "generated-chain-3.js"); + amdDynamicImportPreload('./generated-chain-2', {"generated-leaf.js":"'./generated-leaf'"}, "main.js", "generated-chain-2.js"); + amdDynamicImportPreload(somethingElse, null, "main.js", null); + amdDynamicImportPreload('external-module', null, "main.js", null); + amdDynamicImportPreload('./generated-imports-external', {"external-module":"'external-module'"}, "main.js", "generated-imports-external.js"); + +})); diff --git a/test/chunking-form/samples/render-dynamic-import-extended/_expected/cjs/generated-chain-2.js b/test/chunking-form/samples/render-dynamic-import-extended/_expected/cjs/generated-chain-2.js new file mode 100644 index 000000000..0c45d05f9 --- /dev/null +++ b/test/chunking-form/samples/render-dynamic-import-extended/_expected/cjs/generated-chain-2.js @@ -0,0 +1,9 @@ +'use strict'; + +var leaf = require('./generated-leaf.js'); + +const four = leaf.three + 1; +var fortyTwo = 42; + +exports.default = fortyTwo; +exports.four = four; diff --git a/test/chunking-form/samples/render-dynamic-import-extended/_expected/cjs/generated-chain-3.js b/test/chunking-form/samples/render-dynamic-import-extended/_expected/cjs/generated-chain-3.js new file mode 100644 index 000000000..2185fd468 --- /dev/null +++ b/test/chunking-form/samples/render-dynamic-import-extended/_expected/cjs/generated-chain-3.js @@ -0,0 +1,8 @@ +'use strict'; + +var chain2 = require('./generated-chain-2.js'); +require('./generated-leaf.js'); + +console.log('from import:', chain2.default, chain2.four); + +exports.default = chain2.default; diff --git a/test/chunking-form/samples/render-dynamic-import-extended/_expected/cjs/generated-imports-external.js b/test/chunking-form/samples/render-dynamic-import-extended/_expected/cjs/generated-imports-external.js new file mode 100644 index 000000000..80836fa33 --- /dev/null +++ b/test/chunking-form/samples/render-dynamic-import-extended/_expected/cjs/generated-imports-external.js @@ -0,0 +1,26 @@ +'use strict'; + +var externalModule = require('external-module'); + +function _interopNamespaceDefault(e) { + var n = Object.create(null); + if (e) { + Object.keys(e).forEach(function (k) { + if (k !== 'default') { + var d = Object.getOwnPropertyDescriptor(e, k); + Object.defineProperty(n, k, d.get ? d : { + enumerable: true, + get: function () { return e[k]; } + }); + } + }); + } + n.default = e; + return Object.freeze(n); +} + +var externalModule__namespace = /*#__PURE__*/_interopNamespaceDefault(externalModule); + + + +exports.fromExternal = externalModule__namespace; diff --git a/test/chunking-form/samples/render-dynamic-import-extended/_expected/cjs/generated-leaf.js b/test/chunking-form/samples/render-dynamic-import-extended/_expected/cjs/generated-leaf.js new file mode 100644 index 000000000..e8bd4a629 --- /dev/null +++ b/test/chunking-form/samples/render-dynamic-import-extended/_expected/cjs/generated-leaf.js @@ -0,0 +1,5 @@ +'use strict'; + +var three = 3; + +exports.three = three; diff --git a/test/chunking-form/samples/render-dynamic-import-extended/_expected/cjs/main.js b/test/chunking-form/samples/render-dynamic-import-extended/_expected/cjs/main.js new file mode 100644 index 000000000..c73918854 --- /dev/null +++ b/test/chunking-form/samples/render-dynamic-import-extended/_expected/cjs/main.js @@ -0,0 +1,7 @@ +'use strict'; + +cjsDynamicImportPreload('./generated-chain-3.js', {"generated-chain-2.js":"'./generated-chain-2.js'","generated-leaf.js":"'./generated-leaf.js'"}, "main.js", "generated-chain-3.js"); +cjsDynamicImportPreload('./generated-chain-2.js', {"generated-leaf.js":"'./generated-leaf.js'"}, "main.js", "generated-chain-2.js"); +cjsDynamicImportPreload(somethingElse, null, "main.js", null); +cjsDynamicImportPreload('external-module', null, "main.js", null); +cjsDynamicImportPreload('./generated-imports-external.js', {"external-module":"'external-module'"}, "main.js", "generated-imports-external.js"); diff --git a/test/chunking-form/samples/render-dynamic-import-extended/_expected/es/generated-chain-2.js b/test/chunking-form/samples/render-dynamic-import-extended/_expected/es/generated-chain-2.js new file mode 100644 index 000000000..37e754b15 --- /dev/null +++ b/test/chunking-form/samples/render-dynamic-import-extended/_expected/es/generated-chain-2.js @@ -0,0 +1,6 @@ +import { t as three } from './generated-leaf.js'; + +const four = three + 1; +var fortyTwo = 42; + +export { fortyTwo as default, four }; diff --git a/test/chunking-form/samples/render-dynamic-import-extended/_expected/es/generated-chain-3.js b/test/chunking-form/samples/render-dynamic-import-extended/_expected/es/generated-chain-3.js new file mode 100644 index 000000000..b8e44862a --- /dev/null +++ b/test/chunking-form/samples/render-dynamic-import-extended/_expected/es/generated-chain-3.js @@ -0,0 +1,6 @@ +import fortyTwo, { four } from './generated-chain-2.js'; +import './generated-leaf.js'; + +console.log('from import:', fortyTwo, four); + +export { fortyTwo as default }; diff --git a/test/chunking-form/samples/render-dynamic-import-extended/_expected/es/generated-imports-external.js b/test/chunking-form/samples/render-dynamic-import-extended/_expected/es/generated-imports-external.js new file mode 100644 index 000000000..8e668df8a --- /dev/null +++ b/test/chunking-form/samples/render-dynamic-import-extended/_expected/es/generated-imports-external.js @@ -0,0 +1,2 @@ +import * as externalModule from 'external-module'; +export { externalModule as fromExternal }; diff --git a/test/chunking-form/samples/render-dynamic-import-extended/_expected/es/generated-leaf.js b/test/chunking-form/samples/render-dynamic-import-extended/_expected/es/generated-leaf.js new file mode 100644 index 000000000..f87639251 --- /dev/null +++ b/test/chunking-form/samples/render-dynamic-import-extended/_expected/es/generated-leaf.js @@ -0,0 +1,3 @@ +var three = 3; + +export { three as t }; diff --git a/test/chunking-form/samples/render-dynamic-import-extended/_expected/es/main.js b/test/chunking-form/samples/render-dynamic-import-extended/_expected/es/main.js new file mode 100644 index 000000000..ab5a839e5 --- /dev/null +++ b/test/chunking-form/samples/render-dynamic-import-extended/_expected/es/main.js @@ -0,0 +1,5 @@ +esDynamicImportPreload('./generated-chain-3.js', {"generated-chain-2.js":"'./generated-chain-2.js'","generated-leaf.js":"'./generated-leaf.js'"}, "main.js", "generated-chain-3.js"); +esDynamicImportPreload('./generated-chain-2.js', {"generated-leaf.js":"'./generated-leaf.js'"}, "main.js", "generated-chain-2.js"); +esDynamicImportPreload(somethingElse, null, "main.js", null); +esDynamicImportPreload('external-module', null, "main.js", null); +esDynamicImportPreload('./generated-imports-external.js', {"external-module":"'external-module'"}, "main.js", "generated-imports-external.js"); diff --git a/test/chunking-form/samples/render-dynamic-import-extended/_expected/system/generated-chain-2.js b/test/chunking-form/samples/render-dynamic-import-extended/_expected/system/generated-chain-2.js new file mode 100644 index 000000000..4552092a8 --- /dev/null +++ b/test/chunking-form/samples/render-dynamic-import-extended/_expected/system/generated-chain-2.js @@ -0,0 +1,15 @@ +System.register(['./generated-leaf.js'], (function (exports) { + 'use strict'; + var three; + return { + setters: [function (module) { + three = module.t; + }], + execute: (function () { + + const four = exports("four", three + 1); + var fortyTwo = exports("default", 42); + + }) + }; +})); diff --git a/test/chunking-form/samples/render-dynamic-import-extended/_expected/system/generated-chain-3.js b/test/chunking-form/samples/render-dynamic-import-extended/_expected/system/generated-chain-3.js new file mode 100644 index 000000000..bb84718aa --- /dev/null +++ b/test/chunking-form/samples/render-dynamic-import-extended/_expected/system/generated-chain-3.js @@ -0,0 +1,16 @@ +System.register(['./generated-chain-2.js', './generated-leaf.js'], (function (exports) { + 'use strict'; + var fortyTwo, four; + return { + setters: [function (module) { + fortyTwo = module.default; + four = module.four; + exports("default", module.default); + }, null], + execute: (function () { + + console.log('from import:', fortyTwo, four); + + }) + }; +})); diff --git a/test/chunking-form/samples/render-dynamic-import-extended/_expected/system/generated-imports-external.js b/test/chunking-form/samples/render-dynamic-import-extended/_expected/system/generated-imports-external.js new file mode 100644 index 000000000..c7c231775 --- /dev/null +++ b/test/chunking-form/samples/render-dynamic-import-extended/_expected/system/generated-imports-external.js @@ -0,0 +1,13 @@ +System.register(['external-module'], (function (exports) { + 'use strict'; + return { + setters: [function (module) { + exports("fromExternal", module); + }], + execute: (function () { + + + + }) + }; +})); diff --git a/test/chunking-form/samples/render-dynamic-import-extended/_expected/system/generated-leaf.js b/test/chunking-form/samples/render-dynamic-import-extended/_expected/system/generated-leaf.js new file mode 100644 index 000000000..c3814211d --- /dev/null +++ b/test/chunking-form/samples/render-dynamic-import-extended/_expected/system/generated-leaf.js @@ -0,0 +1,10 @@ +System.register([], (function (exports) { + 'use strict'; + return { + execute: (function () { + + var three = exports("t", 3); + + }) + }; +})); diff --git a/test/chunking-form/samples/render-dynamic-import-extended/_expected/system/main.js b/test/chunking-form/samples/render-dynamic-import-extended/_expected/system/main.js new file mode 100644 index 000000000..a5935983a --- /dev/null +++ b/test/chunking-form/samples/render-dynamic-import-extended/_expected/system/main.js @@ -0,0 +1,14 @@ +System.register([], (function (exports, module) { + 'use strict'; + return { + execute: (function () { + + systemDynamicImportPreload('./generated-chain-3.js', {"generated-chain-2.js":"'./generated-chain-2.js'","generated-leaf.js":"'./generated-leaf.js'"}, "main.js", "generated-chain-3.js"); + systemDynamicImportPreload('./generated-chain-2.js', {"generated-leaf.js":"'./generated-leaf.js'"}, "main.js", "generated-chain-2.js"); + systemDynamicImportPreload(somethingElse, null, "main.js", null); + systemDynamicImportPreload('external-module', null, "main.js", null); + systemDynamicImportPreload('./generated-imports-external.js', {"external-module":"'external-module'"}, "main.js", "generated-imports-external.js"); + + }) + }; +})); diff --git a/test/chunking-form/samples/render-dynamic-import-extended/chain-2.js b/test/chunking-form/samples/render-dynamic-import-extended/chain-2.js new file mode 100644 index 000000000..cbbbe3769 --- /dev/null +++ b/test/chunking-form/samples/render-dynamic-import-extended/chain-2.js @@ -0,0 +1,4 @@ +import three from './leaf.js'; + +export const four = three + 1; +export default 42; diff --git a/test/chunking-form/samples/render-dynamic-import-extended/chain-3.js b/test/chunking-form/samples/render-dynamic-import-extended/chain-3.js new file mode 100644 index 000000000..3f9055eca --- /dev/null +++ b/test/chunking-form/samples/render-dynamic-import-extended/chain-3.js @@ -0,0 +1,3 @@ +import fortyTwo, { four } from './chain-2.js'; +console.log('from import:', fortyTwo, four); +export default fortyTwo; diff --git a/test/chunking-form/samples/render-dynamic-import-extended/imports-external.js b/test/chunking-form/samples/render-dynamic-import-extended/imports-external.js new file mode 100644 index 000000000..d419b8e03 --- /dev/null +++ b/test/chunking-form/samples/render-dynamic-import-extended/imports-external.js @@ -0,0 +1 @@ +export * as fromExternal from 'external-module'; diff --git a/test/chunking-form/samples/render-dynamic-import-extended/leaf.js b/test/chunking-form/samples/render-dynamic-import-extended/leaf.js new file mode 100644 index 000000000..dbb41f0e1 --- /dev/null +++ b/test/chunking-form/samples/render-dynamic-import-extended/leaf.js @@ -0,0 +1 @@ +export default 3; diff --git a/test/chunking-form/samples/render-dynamic-import-extended/main.js b/test/chunking-form/samples/render-dynamic-import-extended/main.js new file mode 100644 index 000000000..dca5409be --- /dev/null +++ b/test/chunking-form/samples/render-dynamic-import-extended/main.js @@ -0,0 +1,5 @@ +import('./chain-3.js'); +import('./chain-2.js'); +import(somethingElse); +import('external-module'); +import('./imports-external.js'); From ab7bfa8fe9c25e41cc62058fa2dcde6b321fd51d Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Mon, 17 Mar 2025 09:13:37 +0100 Subject: [PATCH 6/7] 4.36.0 --- CHANGELOG.md | 22 ++++++++++++++++++++++ browser/package.json | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 70c4a8a50..bdf9d3556 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,27 @@ # rollup changelog +## 4.36.0 + +_2025-03-17_ + +### Features + +- Extend `renderDynamicImport` hook to provide information about static dependencies of the imported module (#5870) +- Export several additional types used by Vite (#5879) + +### Bug Fixes + +- Do not merge chunks if that would create a top-level await cycle between chunks (#5843) + +### Pull Requests + +- [#5843](https://github.com/rollup/rollup/pull/5843): avoiding top level await circular (@TrickyPi, @lukastaegert) +- [#5870](https://github.com/rollup/rollup/pull/5870): draft for extended renderDynamicImport hook (@iczero, @lukastaegert) +- [#5876](https://github.com/rollup/rollup/pull/5876): Update axios overrides to 1.8.2 (@vadym-khodak) +- [#5877](https://github.com/rollup/rollup/pull/5877): chore(deps): update dependency eslint-plugin-vue to v10 (@renovate[bot]) +- [#5878](https://github.com/rollup/rollup/pull/5878): fix(deps): lock file maintenance minor/patch updates (@renovate[bot]) +- [#5879](https://github.com/rollup/rollup/pull/5879): fix: export types (@sxzz) + ## 4.35.0 _2025-03-08_ diff --git a/browser/package.json b/browser/package.json index f4e8baf94..38280b76d 100644 --- a/browser/package.json +++ b/browser/package.json @@ -1,6 +1,6 @@ { "name": "@rollup/browser", - "version": "4.35.0", + "version": "4.36.0", "description": "Next-generation ES module bundler browser build", "main": "dist/rollup.browser.js", "module": "dist/es/rollup.browser.js", diff --git a/package-lock.json b/package-lock.json index 50646f788..3d3b62bdc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "rollup", - "version": "4.35.0", + "version": "4.36.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "rollup", - "version": "4.35.0", + "version": "4.36.0", "license": "MIT", "dependencies": { "@types/estree": "1.0.6" diff --git a/package.json b/package.json index d954847dd..0c4968a03 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rollup", - "version": "4.35.0", + "version": "4.36.0", "description": "Next-generation ES module bundler", "main": "dist/rollup.js", "module": "dist/es/rollup.js", From 3166f524506e518e154cdf9f62e3d7820f4b4570 Mon Sep 17 00:00:00 2001 From: waynzh Date: Wed, 19 Mar 2025 10:04:26 +0800 Subject: [PATCH 7/7] docs(cn): resolve conflicts --- docs/plugin-development/index.md | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/docs/plugin-development/index.md b/docs/plugin-development/index.md index 6cf116767..3b3ad38e8 100644 --- a/docs/plugin-development/index.md +++ b/docs/plugin-development/index.md @@ -1084,19 +1084,13 @@ interface ImportedExternalChunk { } ``` -<<<<<<< HEAD -此钩子通过提供导入表达式参数左侧(`import(`)和右侧(`)`)的代码替换,提供了对动态导入如何呈现的细粒度控制。返回`null`将推迟到此类型的其他钩子,最终呈现特定于格式的默认值。 +请参阅 [`chunkFileNames`](../configuration-options/index.md#output-chunkfilenames) 选项,以了解 `PreRenderedChunk` 类型的相关信息。 -`format` 是呈现的输出格式, `moduleId` 是执行动态导入的模块的 ID。如果导入可以解析为内部或外部 ID,则 `targetModuleId` 将设置为此 ID,否则将为 `null`。如果动态导入包含由 [`resolveDynamicImport`](#resolvedynamicimport) 钩子解析为替换字符串的非字符串表达式,则 `customResolution` 将包含该字符串。 -======= -See the [`chunkFileNames`](../configuration-options/index.md#output-chunkfilenames) option for the `PreRenderedChunk` type. +这个钩子函数提供了对动态导入渲染的精细控制,它通过替换导入表达式参数左侧(`import(`)和右(`)`)侧的代码实现这一功能。如果返回 `null`,则会降级到其他同类型的钩子函数,最终渲染出特定格式的默认值。 -This hook provides fine-grained control over how dynamic imports are rendered by providing replacements for the code to the left (`import(`) and right (`)`) of the argument of the import expression. Returning `null` defers to other hooks of this type and ultimately renders a format-specific default. +`format` 是渲染的输出格式,`moduleId` 是进行动态导入的模块的 id。如果导入可以被解析为内部或外部 id,那么 `targetModuleId` 将被设置为这个 id,否则它将是 `null`。如果动态导入包含了一个非字符串表达式,这个表达式被 [`resolveDynamicImport`](#resolvedynamicimport) 钩子函数解析为一个替换字符串,那么 `customResolution` 将包含那个字符串。`chunk` 和 `targetChunk` 分别提供了执行导入的块和被导入的块(目标块)的额外信息。`getTargetChunkImports` 返回一个数组,包含了被目标块导入的块。如果目标块未解析或是外部的,`targetChunk` 将为 null,`getTargetChunkImports` 也将返回 null。 -`format` is the rendered output format, `moduleId` the id of the module performing the dynamic import. If the import could be resolved to an internal or external id, then `targetModuleId` will be set to this id, otherwise it will be `null`. If the dynamic import contained a non-string expression that was resolved by a [`resolveDynamicImport`](#resolvedynamicimport) hook to a replacement string, then `customResolution` will contain that string. `chunk` and `targetChunk` provide additional information about the chunk performing the import and the chunk being imported (the target chunk), respectively. `getTargetChunkImports` returns an array containing chunks which are imported by the target chunk. If the target chunk is unresolved or external, `targetChunk` will be null and `getTargetChunkImports` will return null. - -The `PreRenderedChunkWithFileName` type is identical to the `PreRenderedChunk` type except for the addition of the `fileName` field, which contains the path and file name of the chunk. `fileName` may contain a placeholder if the chunk file name format contains a hash. ->>>>>>> ab7bfa8fe9c25e41cc62058fa2dcde6b321fd51d +`PreRenderedChunkWithFileName` 类型与 `PreRenderedChunk` 类型相同,只是多了一个 `fileName` 字段,这个字段包含了块的路径和文件名。如果块文件名格式包含了哈希,`fileName` 可能会包含一个占位符。 以下代码将使用自定义处理程序替换所有动态导入,添加 `import.meta.url` 作为第二个参数,以允许处理程序正确解析相对导入: @@ -1148,12 +1142,9 @@ function retainImportExpressionPlugin() { } ``` -<<<<<<< HEAD -请注意,当此钩子在非 ES 格式中重写动态导入时,不会生成任何交互代码以确保例如默认导出可用作`.default`。插件有责任确保重写的动态导入返回一个 Promise,该 Promise 解析为适当的命名空间对象。 -======= -When a dynamic import occurs, the browser will fetch the requested module and parse it. If the target module is discovered to have imports and they have not already been fetched, the browser must perform more network requests before it can execute the module. This will incur the latency of an additional network round trip. For static modules, Rollup will hoist transitive dependencies ([`hoistTransitiveDependencies`](../configuration-options/index.md#output-hoisttransitiveimports)) to prevent this from occuring. However, dependency hoisting is currently not automatically performed for dynamic imports. +当进行动态导入时,浏览器会获取并解析请求的模块。如果目标模块被发现有导入,并且这些导入还未被获取,浏览器需要进行更多的网络请求才能执行该模块。这将导致额外一轮网络往返的延迟。对于静态模块,Rollup 会提升转移依赖([`hoistTransitiveDependencies`](../configuration-options/index.md#output-hoisttransitiveimports)),以防止这种情况发生。然而,目前还没有自动为动态导入执行依赖提升的操作。 -The following plugin can achieve similar preloading behavior for dynamic imports: +以下的插件可以实现类似于动态导入预加载的功能: ```js twoslash // ---cut-start--- @@ -1196,8 +1187,7 @@ import('./lib.js'); ``` -Note that when this hook rewrites dynamic imports in non-ES formats, no interop code to make sure that e.g. the default export is available as `.default` is generated. It is the responsibility of the plugin to make sure the rewritten dynamic import returns a Promise that resolves to a proper namespace object. ->>>>>>> ab7bfa8fe9c25e41cc62058fa2dcde6b321fd51d +请注意,当此钩子在非 ES 格式中重写动态导入时,不会生成任何交互代码以确保例如默认导出可用作 `.default`。插件有责任确保重写的动态导入返回一个 Promise,该 Promise 解析为适当的命名空间对象。 ### renderError