diff --git a/docs/05-plugin-development.md b/docs/05-plugin-development.md index d67b216587b..ef2def09112 100644 --- a/docs/05-plugin-development.md +++ b/docs/05-plugin-development.md @@ -1055,7 +1055,7 @@ Note the convention that custom options should be added using a property corresp #### Custom module meta-data -Plugins can annotate modules with custom meta-data which can be accessed by themselves and other plugins via the [`resolveId`](guide/en/#resolveid), [`load`](guide/en/#load), and [`transform`](guide/en/#transform) hooks. This meta-data should always be JSON.stringifyable and will be persisted in the cache e.g. in watch mode. +Plugins can annotate modules with custom meta-data which can be set by themselves and other plugins via the [`resolveId`](guide/en/#resolveid), [`load`](guide/en/#load), and [`transform`](guide/en/#transform) hooks and accessed via [`this.getModuleInfo`](guide/en/#thisgetmoduleinfo), [`this.load`](guide/en/#thisload) and the [`moduleParsed`](guide/en/#moduleparsed) hook. This meta-data should always be JSON.stringifyable and will be persisted in the cache e.g. in watch mode. ```js function annotatingPlugin() { @@ -1087,6 +1087,26 @@ Note the convention that plugins that add or modify data should use a property c If several plugins add meta-data or meta-data is added in different hooks, then these `meta` objects will be merged shallowly. That means if plugin `first` adds `{meta: {first: {resolved: "first"}}}` in the resolveId hook and `{meta: {first: {loaded: "first"}}}` in the load hook while plugin `second` adds `{meta: {second: {transformed: "second"}}}` in the `transform` hook, then the resulting `meta` object will be `{first: {loaded: "first"}, second: {transformed: "second"}}`. Here the result of the `resolveId` hook will be overwritten by the result of the `load` hook as the plugin was both storing them under its `first` top-level property. The `transform` data of the other plugin on the other hand will be placed next to it. +The `meta` object of a module is created as soon as Rollup starts loading a module and is updated for each lifecycle hook of the module. If you store a reference to this object, you can also update it manually. To access the meta object of a module that has not been loaded yet, you can trigger its creation and loading the module via [`this.load`](guide/en/#thisload): + +```js +function plugin() { + return { + name: 'test', + buildStart() { + // trigger loading a module. We could also pass an initial "meta" object + // here, but it would be ignored if the module was already loaded via + // other means + this.load({ id: 'my-id' }); + // the module info is now available, we do not need to await this.load + const meta = this.getModuleInfo('my-id').meta; + // we can also modify meta manually now + meta.test = { some: 'data' }; + } + }; +} +``` + #### Direct plugin communication For any other kind of inter-plugin communication, we recommend the pattern below. Note that `api` will never conflict with any upcoming plugin hooks. diff --git a/src/Module.ts b/src/Module.ts index 9af5446b1e7..361b9300f51 100644 --- a/src/Module.ts +++ b/src/Module.ts @@ -840,7 +840,7 @@ export default class Module { this.info.syntheticNamedExports = syntheticNamedExports; } if (meta != null) { - this.info.meta = { ...this.info.meta, ...meta }; + Object.assign(this.info.meta, meta); } } diff --git a/src/ModuleLoader.ts b/src/ModuleLoader.ts index 0d774a8bad6..515ebf233ad 100644 --- a/src/ModuleLoader.ts +++ b/src/ModuleLoader.ts @@ -204,7 +204,7 @@ export class ModuleLoader { return { external, id: resolvedId.id, - meta: resolvedId.meta || EMPTY_OBJECT, + meta: resolvedId.meta || {}, moduleSideEffects: resolvedId.moduleSideEffects ?? this.hasModuleSideEffects(resolvedId.id, !!external), syntheticNamedExports: resolvedId.syntheticNamedExports ?? false @@ -555,7 +555,7 @@ export class ModuleLoader { return { external: true, id: source, - meta: EMPTY_OBJECT, + meta: {}, moduleSideEffects: this.hasModuleSideEffects(source, true), syntheticNamedExports: false }; diff --git a/test/function/samples/modify-meta/_config.js b/test/function/samples/modify-meta/_config.js new file mode 100644 index 00000000000..daf0ff85d0f --- /dev/null +++ b/test/function/samples/modify-meta/_config.js @@ -0,0 +1,66 @@ +const assert = require('assert'); +const path = require('path'); +const ID_MAIN = path.join(__dirname, 'main.js'); + +let initialMeta; + +module.exports = { + description: 'allows to freely modify moduleInfo.meta and maintain object identity', + options: { + plugins: [ + { + name: 'first', + buildStart() { + this.load({ id: ID_MAIN }); + initialMeta = this.getModuleInfo(ID_MAIN).meta; + initialMeta.buildStart = true; + }, + load(id) { + assert.strictEqual(id, ID_MAIN); + const meta = this.getModuleInfo(ID_MAIN).meta; + assert.deepStrictEqual(meta, { buildStart: true }, 'load'); + assert.strictEqual(meta, initialMeta); + meta.load1a = true; + return { code: `assert.ok(true);`, meta: { load1b: true } }; + }, + transform(code, id) { + assert.strictEqual(id, ID_MAIN); + const meta = this.getModuleInfo(ID_MAIN).meta; + assert.deepStrictEqual( + meta, + { buildStart: true, load1a: true, load1b: true }, + 'transform' + ); + assert.strictEqual(meta, initialMeta); + meta.transform1a = true; + return { code: `assert.ok(true);`, meta: { transform1b: true } }; + }, + buildEnd(error) { + if (error) { + throw error; + } + const meta = this.getModuleInfo(ID_MAIN).meta; + assert.deepStrictEqual( + meta, + { + buildStart: true, + load1a: true, + load1b: true, + transform1a: true, + transform1b: true, + transform2: true + }, + 'buildEnd' + ); + } + }, + { + name: 'second', + transform(code, id) { + assert.strictEqual(id, ID_MAIN); + return { code: `assert.ok(true);`, meta: { transform2: true } }; + } + } + ] + } +}; diff --git a/test/function/samples/modify-meta/main.js b/test/function/samples/modify-meta/main.js new file mode 100644 index 00000000000..cc1d88a24fa --- /dev/null +++ b/test/function/samples/modify-meta/main.js @@ -0,0 +1 @@ +assert.ok(true); diff --git a/test/incremental/index.js b/test/incremental/index.js index ad84de892da..1595ca248d7 100644 --- a/test/incremental/index.js +++ b/test/incremental/index.js @@ -293,18 +293,17 @@ describe('incremental', () => { }, load(id) { - assert.deepStrictEqual(this.getModuleInfo(id).meta, { test: { resolved: id } }); return { code: modules[id], meta: { test: { loaded: id } } }; }, transform(code, id) { transformCalls++; - assert.deepStrictEqual(this.getModuleInfo(id).meta, { test: { loaded: id } }); + assert.deepStrictEqual(this.getModuleInfo(id).meta, { test: { loaded: id } }, 'transform'); return { code, meta: { test: { transformed: id } } }; }, moduleParsed({ id, meta }) { - assert.deepStrictEqual(meta, { test: { transformed: id } }); + assert.deepStrictEqual(meta, { test: { transformed: id } }, 'moduleParsed'); moduleParsedCalls++; }, @@ -314,7 +313,8 @@ describe('incremental', () => { [ { id: 'entry', meta: { test: { transformed: 'entry' } } }, { id: 'foo', meta: { test: { transformed: 'foo' } } } - ] + ], + 'buildEnd' ); } };