Skip to content

Side effects when using the spread operator to merge objects #1017

Open
@serkodev

Description

Currently, if we use the spread operator (...) to merge objects as shown below, it causes side effects and cannot be tree-shaken:

const ParentSchema = v.object({
  foo: v.string(),
});

const LoginSchema = v.object({
  ...ParentSchema.entries, // <- side effect
  email: v.pipe(v.string(), v.email()),
  password: v.pipe(v.string(), v.minLength(8)),
});

Playground
https://stackblitz.com/edit/node-gq8gcqea?file=index.js

The reason is that bundlers like Rollup set propertyReadSideEffects to true by default, which means the spread operator is treated as a side effect.

We can set the treeshake.preset to "smallest" to disable propertyReadSideEffects and make it tree-shakeable but this may cause other issues.

Solution

I wonder if we could have a v.merge function with a @__NO_SIDE_EFFECTS__ annotation for merging objects, making it possible for bundlers to tree-shake it:

// @__NO_SIDE_EFFECTS__
function merge(a, b) {
  return v.object({
    ...a.entries,
    ...b.entries,
  });
}

Usage

const ParentSchema = v.object({
  foo: v.string(),
});

const LoginSchema = v.merge(
  ParentSchema,
  v.object({
    email: v.pipe(v.string(), v.email()),
    password: v.pipe(v.string(), v.minLength(8)),
  })
);

It would be even better if the v.merge function supported an unlimited number of arguments to merge multiple objects at once.

Alternative: Supports input array in v.object

Also, I am wondering if we can support input an array of entries in v.object, like:

// @__NO_SIDE_EFFECTS__
function object(entries, message) {
  if (Array.isArray(entries)) {
    return v.object(
      entries.reduce((acc, current) => ({ ...acc, ...current }), {}),
      message
    );
  }
  // ... original implementation
}

Usage

const ParentSchema = v.object({
  foo: v.string(),
});

const LoginSchema = v.object([
  ParentSchema.entries,
  {
    email: v.pipe(v.string(), v.email()),
    password: v.pipe(v.string(), v.minLength(8)),
  }
]);

It can also be tree-shaken as the spread operator is not used.


Thanks @antfu for helping clarify this issue.

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions