Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
# rollup changelog

## 4.57.0

_2026-01-27_

### Features

- Add import attributes to all plugin hooks that did not provide them yet (#5700)
- Deprecate returning import attributes from `load` or `transform` hooks as that will no longer be supported with rollup 5 (#5700)

### Pull Requests

- [#5700](https://github.com/rollup/rollup/pull/5700): extend more hooks to include import attributes and add warnings (@TrickyPi, @lukastaegert)
- [#6243](https://github.com/rollup/rollup/pull/6243): fix(deps): update swc monorepo (major) (@renovate[bot], @lukastaegert)
- [#6244](https://github.com/rollup/rollup/pull/6244): fix(deps): lock file maintenance minor/patch updates (@renovate[bot], @lukastaegert)
- [#6245](https://github.com/rollup/rollup/pull/6245): chore(deps): lock file maintenance (@renovate[bot])
- [#6246](https://github.com/rollup/rollup/pull/6246): Refactor to reduce Rollup 5 upgrade diff (@lukastaegert)

## 4.56.0

_2026-01-22_
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": "4.56.0",
"version": "4.57.0",
"description": "Next-generation ES module bundler browser build",
"main": "dist/rollup.browser.js",
"module": "dist/es/rollup.browser.js",
Expand Down
134 changes: 117 additions & 17 deletions docs/plugin-development/index.md

Large diffs are not rendered by default.

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": "4.56.0",
"version": "4.57.0",
"description": "Next-generation ES module bundler",
"main": "dist/rollup.js",
"module": "dist/es/rollup.js",
Expand Down
111 changes: 79 additions & 32 deletions src/ModuleLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ import {
logUnresolvedEntry,
logUnresolvedImplicitDependant,
logUnresolvedImport,
logUnresolvedImportTreatedAsExternal
logUnresolvedImportTreatedAsExternal,
warnDeprecation
} from './utils/logs';
import {
doAttributesDiffer,
Expand All @@ -42,6 +43,7 @@ import relativeId from './utils/relativeId';
import { resolveId } from './utils/resolveId';
import stripBom from './utils/stripBom';
import transform from './utils/transform';
import { URL_LOAD } from './utils/urls';

export interface UnresolvedModule {
fileName: string | null;
Expand All @@ -56,6 +58,7 @@ export type ModuleLoaderResolveId = (
customOptions: CustomPluginOptions | undefined,
isEntry: boolean | undefined,
attributes: Record<string, string>,
importerAttributes: Record<string, string> | undefined,
skip?: readonly { importer: string | undefined; plugin: Plugin; source: string }[] | null
) => Promise<ResolvedId | null>;

Expand Down Expand Up @@ -107,7 +110,7 @@ export class ModuleLoader {
const result = this.extendLoadModulesPromise(
Promise.all(
unresolvedModules.map(id =>
this.loadEntryModule(id, false, undefined, null, isAddForManualChunks)
this.loadEntryModule(id, false, undefined, null, isAddForManualChunks, undefined)
)
)
);
Expand All @@ -130,7 +133,7 @@ export class ModuleLoader {
const newEntryModules = await this.extendLoadModulesPromise(
Promise.all(
unresolvedEntryModules.map(({ id, importer }) =>
this.loadEntryModule(id, true, importer, null)
this.loadEntryModule(id, true, importer, null, undefined, undefined)
)
).then(entryModules => {
for (const [index, entryModule] of entryModules.entries()) {
Expand Down Expand Up @@ -212,6 +215,7 @@ export class ModuleLoader {
customOptions,
isEntry,
attributes,
importerAttributes,
skip = null
) =>
this.getResolvedIdWithDefaults(
Expand All @@ -228,6 +232,7 @@ export class ModuleLoader {
customOptions,
typeof isEntry === 'boolean' ? isEntry : !importer,
attributes,
importerAttributes,
this.options.fs
),
importer,
Expand All @@ -242,32 +247,44 @@ export class ModuleLoader {
): Promise<Module> {
const chunkNamePriority = this.nextChunkNamePriority++;
return this.extendLoadModulesPromise(
this.loadEntryModule(unresolvedModule.id, false, unresolvedModule.importer, null).then(
async entryModule => {
addChunkNamesToModule(entryModule, unresolvedModule, false, chunkNamePriority);
if (!entryModule.info.isEntry) {
const implicitlyLoadedAfterModules = await Promise.all(
implicitlyLoadedAfter.map(id =>
this.loadEntryModule(id, false, unresolvedModule.importer, entryModule.id)
this.loadEntryModule(
unresolvedModule.id,
false,
unresolvedModule.importer,
null,
undefined,
undefined
).then(async entryModule => {
addChunkNamesToModule(entryModule, unresolvedModule, false, chunkNamePriority);
if (!entryModule.info.isEntry) {
const implicitlyLoadedAfterModules = await Promise.all(
implicitlyLoadedAfter.map(id =>
this.loadEntryModule(
id,
false,
unresolvedModule.importer,
entryModule.id,
undefined,
undefined
)
);
// We need to check again if this is still an entry module as these
// changes need to be performed atomically to avoid race conditions
// if the same module is re-emitted as an entry module.
// The inverse changes happen in "handleExistingModule"
if (!entryModule.info.isEntry) {
this.implicitEntryModules.add(entryModule);
for (const module of implicitlyLoadedAfterModules) {
entryModule.implicitlyLoadedAfter.add(module);
}
for (const dependant of entryModule.implicitlyLoadedAfter) {
dependant.implicitlyLoadedBefore.add(entryModule);
}
)
);
// We need to check again if this is still an entry module as these
// changes need to be performed atomically to avoid race conditions
// if the same module is re-emitted as an entry module.
// The inverse changes happen in "handleExistingModule"
if (!entryModule.info.isEntry) {
this.implicitEntryModules.add(entryModule);
for (const module of implicitlyLoadedAfterModules) {
entryModule.implicitlyLoadedAfter.add(module);
}
for (const dependant of entryModule.implicitlyLoadedAfter) {
dependant.implicitlyLoadedBefore.add(entryModule);
}
}
return entryModule;
}
)
return entryModule;
})
);
}

Expand All @@ -279,8 +296,21 @@ export class ModuleLoader {
let source: LoadResult;
try {
source = await this.graph.fileOperationQueue.run(async () => {
const content = await this.pluginDriver.hookFirst('load', [id]);
if (content !== null) return content;
const content = await this.pluginDriver.hookFirst('load', [
id,
{ attributes: module.info.attributes }
]);
if (content !== null) {
if (typeof content === 'object' && content.attributes) {
warnDeprecation(
'Returning attributes from the "load" hook is forbidden.',
URL_LOAD,
false,
this.options
);
}
return content;
}
this.graph.watchFiles[id] = true;
return (await this.options.fs.readFile(id, { encoding: 'utf8' })) as string;
});
Expand All @@ -306,6 +336,7 @@ export class ModuleLoader {
!(await this.pluginDriver.hookFirst('shouldTransformCachedModule', [
{
ast: cachedModule.ast,
attributes: cachedModule.attributes,
code: cachedModule.code,
id: cachedModule.id,
meta: cachedModule.meta,
Expand All @@ -323,7 +354,7 @@ export class ModuleLoader {
} else {
module.updateOptions(sourceDescription);
await module.setSource(
await transform(sourceDescription, module, this.pluginDriver, this.options.onLog)
await transform(sourceDescription, module, this.pluginDriver, this.options)
);
}
}
Expand Down Expand Up @@ -586,7 +617,14 @@ export class ModuleLoader {
(module.resolvedIds[source] =
module.resolvedIds[source] ||
this.handleInvalidResolvedId(
await this.resolveId(source, module.id, EMPTY_OBJECT, false, attributes),
await this.resolveId(
source,
module.id,
EMPTY_OBJECT,
false,
attributes,
module.info.attributes
),
source,
module.id,
attributes
Expand Down Expand Up @@ -666,7 +704,8 @@ export class ModuleLoader {
isEntry: boolean,
importer: string | undefined,
implicitlyLoadedBefore: string | null,
isLoadForManualChunks = false
isLoadForManualChunks = false,
importerAttributes: Record<string, string> | undefined
): Promise<Module> {
const resolveIdResult = await resolveId(
unresolvedId,
Expand All @@ -678,6 +717,7 @@ export class ModuleLoader {
EMPTY_OBJECT,
true,
EMPTY_OBJECT,
importerAttributes,
this.options.fs
);
if (resolveIdResult == null) {
Expand Down Expand Up @@ -719,7 +759,7 @@ export class ModuleLoader {
const resolution = await this.pluginDriver.hookFirst('resolveDynamicImport', [
specifier,
importer,
{ attributes }
{ attributes, importerAttributes: module.info.attributes }
]);
if (typeof specifier !== 'string') {
if (typeof resolution === 'string') {
Expand Down Expand Up @@ -750,7 +790,14 @@ export class ModuleLoader {
return existingResolution;
}
return (module.resolvedIds[specifier] = this.handleInvalidResolvedId(
await this.resolveId(specifier, module.id, EMPTY_OBJECT, false, attributes),
await this.resolveId(
specifier,
module.id,
EMPTY_OBJECT,
false,
attributes,
module.info.attributes
),
specifier,
module.id,
attributes
Expand Down
2 changes: 2 additions & 0 deletions src/ast/nodes/ImportExpression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,8 @@ export default class ImportExpression extends NodeBase {
},
moduleId: scope.context.module.id,
targetChunk: targetChunk ? getChunkInfoWithPath(targetChunk) : null,
targetModuleAttributes:
resolution && typeof resolution !== 'string' ? resolution.info.attributes : {},
targetModuleId: resolution && typeof resolution !== 'string' ? resolution.id : null
}
]);
Expand Down
9 changes: 6 additions & 3 deletions src/ast/nodes/MetaProperty.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,10 @@ export default class MetaProperty extends NodeBase {
start,
end
} = this;
const { id: moduleId } = module;
const {
id: moduleId,
info: { attributes }
} = module;

if (name !== IMPORT) return;
const chunkId = preliminaryChunkId!;
Expand All @@ -97,7 +100,7 @@ export default class MetaProperty extends NodeBase {
const isUrlObject = !!metaProperty?.startsWith(FILE_OBJ_PREFIX);
const replacement =
pluginDriver.hookFirstSync('resolveFileUrl', [
{ chunkId, fileName, format, moduleId, referenceId, relativePath }
{ attributes, chunkId, fileName, format, moduleId, referenceId, relativePath }
]) || relativeUrlMechanisms[format](relativePath, isUrlObject);

code.overwrite(
Expand All @@ -111,7 +114,7 @@ export default class MetaProperty extends NodeBase {

let replacement = pluginDriver.hookFirstSync('resolveImportMeta', [
metaProperty,
{ chunkId, format, moduleId }
{ attributes, chunkId, format, moduleId }
]);
if (!replacement) {
replacement = importMetaMechanisms[format]?.(metaProperty, { chunkId, snippets });
Expand Down
Loading
Loading