Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make initial ModuleInfo.meta mutable and maintain object identity #4328

Merged
merged 3 commits into from Jan 4, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
22 changes: 21 additions & 1 deletion docs/05-plugin-development.md
Expand Up @@ -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() {
Expand Down Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion src/Module.ts
Expand Up @@ -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);
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/ModuleLoader.ts
Expand Up @@ -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
Expand Down Expand Up @@ -555,7 +555,7 @@ export class ModuleLoader {
return {
external: true,
id: source,
meta: EMPTY_OBJECT,
meta: {},
moduleSideEffects: this.hasModuleSideEffects(source, true),
syntheticNamedExports: false
};
Expand Down
66 changes: 66 additions & 0 deletions 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 } };
}
}
]
}
};
1 change: 1 addition & 0 deletions test/function/samples/modify-meta/main.js
@@ -0,0 +1 @@
assert.ok(true);
8 changes: 4 additions & 4 deletions test/incremental/index.js
Expand Up @@ -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++;
},

Expand All @@ -314,7 +313,8 @@ describe('incremental', () => {
[
{ id: 'entry', meta: { test: { transformed: 'entry' } } },
{ id: 'foo', meta: { test: { transformed: 'foo' } } }
]
],
'buildEnd'
);
}
};
Expand Down