Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
vue-functional-data-merge
Advanced tools
Vue.js util for intelligently merging data passed to functional components.
The vue-functional-data-merge package is a utility for merging data objects in Vue.js functional components. It helps in combining props, attributes, and listeners in a way that is consistent with Vue's internal behavior.
Merging Props
This feature allows you to merge props from multiple data objects into a single data object. This is useful when you need to combine props from different sources in a functional component.
const { mergeData } = require('vue-functional-data-merge');
const data1 = { props: { foo: 'bar' } };
const data2 = { props: { baz: 'qux' } };
const mergedData = mergeData(data1, data2);
console.log(mergedData); // { props: { foo: 'bar', baz: 'qux' } }
Merging Attributes
This feature allows you to merge attributes from multiple data objects into a single data object. This is useful when you need to combine attributes from different sources in a functional component.
const { mergeData } = require('vue-functional-data-merge');
const data1 = { attrs: { id: 'foo' } };
const data2 = { attrs: { class: 'bar' } };
const mergedData = mergeData(data1, data2);
console.log(mergedData); // { attrs: { id: 'foo', class: 'bar' } }
Merging Event Listeners
This feature allows you to merge event listeners from multiple data objects into a single data object. This is useful when you need to combine event listeners from different sources in a functional component.
const { mergeData } = require('vue-functional-data-merge');
const data1 = { on: { click: () => console.log('clicked 1') } };
const data2 = { on: { click: () => console.log('clicked 2') } };
const mergedData = mergeData(data1, data2);
console.log(mergedData); // { on: { click: [ [Function], [Function] ] } }
Lodash's merge function is a general-purpose utility for deep merging objects. While it is not specific to Vue.js, it can be used to merge props, attributes, and listeners in a similar way. However, it does not handle Vue-specific nuances like merging event listeners into arrays.
Deepmerge is another general-purpose utility for deep merging objects. Like lodash.merge, it is not specific to Vue.js but can be used to merge data objects. It also does not handle Vue-specific behavior like merging event listeners.
Vue.js util for intelligently merging data passed to functional components. (1K => 0.5K gzipped)
Load the util from npm:
# NPM:
npm i vue-functional-data-merge
# Yarn:
yarn add vue-functional-data-merge
Now import and use it in your functional component declaration:
// MyFunctionalComponent.js
// ESM
import { mergeData } from "vue-functional-data-merge";
// Common JS
const { mergeData } = require("vue-functional-data-merge/dist/lib.common.js");
export default {
name: "my-functional-component",
functional: true,
props: ["foo", "bar", "baz"],
render(h, { props, data, children }) {
const componentData = {
staticClass: "fn-component", // concatenates all static classes
class: {
// object|Array|string all get merged and preserved
active: props.foo,
"special-class": props.bar,
},
attrs: {
id: "my-functional-component", // now overrides any id placed on the component
},
on: {
// Event handlers are merged to an array of handlers at each event.
// The last data object passed to `mergeData` will have it's event handlers called first.
// Right-most arguments are prepended to event handler array.
click(e) {
alert(props.baz);
},
},
};
return h("div", mergeData(data, componentData), children);
},
};
When writing functional Vue components, the render function receives a
context.data
object
(see vue docs).
This object that contains the entire data object passed to the component (the
shape of which
can be found here).
In order to write flexible components, the data object used to create the
component must be merged with the data received. If not, only the properties
defined by the component will be rendered.
Consider this example:
// MyBtn.js
export default {
name: "my-btn",
props: ["variant"],
functional: true,
render(h, { props, children }) {
return h(
"button",
{
staticClass: "btn",
class: [`btn-${props.variant}`],
attrs: { type: "button" },
},
children
);
},
};
This exports a functional button component that applies a base .btn
class and
a .btn-<variant>
class based on the variant
prop passed to the component.
It's just a simple wrapper around some Bootstrap styling to make repetitive
usage simpler. Usage would look like this:
<template>
<form>
<input type="text" placeholder="Name" required>
<input type="email" placeholder="email" required>
<my-btn variant="primary" type="submit" id="form-submit-btn" @click="onClick">Submit</my-btn>
</form>
</template>
We've used our Bootstrap button component in a form and conveniently applied the
primary
variant, but we also wanted to change the button type
from button
to submit
, give it an id
, and attach a click handler. This won't work
because we haven't passed the attributes, listeners, etc. to the create element
call in the component's render function.
To fix this, we might extract out props, merge listeners/attributes, etc. This
works well, but gets verbose fast when attempting to support all dom attributes,
event listeners, etc. One might think to simply use Object spread or
Object.assign
to solve this like so:
return h("button", { ...context.data, ...componentData }, children);
Now when we try to add any dom attributes, Object spread is essentially performing something like this:
Object.assign(
{},
{
props: { variant: "primary" },
attrs: { id: "form-submit-btn", type: "submit" }
on: { click: onClick }
},
{
staticClass: "btn",
class: [`btn-${props.variant}`],
attrs: { type: "button" },
on: {
click() {
alert("Hello from MyBtn!")
}
}
}
)
The component data will wipe out all the context's attrs
and on
handlers as
Object.assign
merges these properties. This is where the mergeData
util can
help you. It will dig into the nested properties of the context.data
and apply
different merge strategies for each data property. mergeData
works like a
nested Object.assign
in that the util has a variadic argument length—you
can pass any number of arguments to it, and they will all be merged from left to
right (the right most arguments taking merge priority). You don't have to pass a
new target object as the first argument, as the return value will always be a
fresh object.
You may run into cases where you are using a functional component in another component with scoped styles. This would look something like this:
<template>
<button class="my-class">
<slot></slot>
</button>
</template>
<style scoped>
.my-class {
text-align: center;
}
</style>
This will generate data attributes on the component elements and the css selector.
<style>
.my-class[data-v-f3f3eg9] {
text-align: center;
}
</style>
<button data-v-f3f3eg9 class="my-class">
Click me!
</button>
When a parent component with scoped styles makes use of a functional component,
the data attribute won't be passed down automatically. Instead, you must pull
this attribute out manually and add it to the VNodeData
used in a render
function's createElement
call. Doing this requires reaching into Vue
internals, which can be risky due to the private nature of the API and its
potential to change. For that reason, this is not supported in this util.
However, this util can make that manual merging easier by conforming to the
VNodeData
shape required by mergeData
and Vue itself. Here is an example of
a helper function to manually extract a parent's style scope id and
conditionally apply it in the functional component's render function.
const FunctionalComponent = {
functional: true,
render(createElement, context) {
let { parent, data, children } = context;
let componentData = { class: "my-class" };
return createElement(
"button",
mergeData(data, getScopedStyleData(parent), componentData),
children
);
},
};
/**
* @param {Vue} parent
* @returns {VNodeData}
*/
export function getScopedStyleData(parent) {
let data = { attrs: {} };
if (parent.$options._scopeId) {
data.attrs[`data-v-${parent.$options._scopeId}`] = "";
}
return data;
}
This util was written with performance in mind. Since functional components are
perfect for components that are stateless and have many nodes rendered, the
mergeData
util is expected to be called extensively. As such, minimal variable
allocations are made as well as minimal internal function calls (for loops are
preferred over map
, reduce
, & forEach
to avoid adding stack frames).
TypeScript is used with Vue typings to ensure the most accurate merge strategy
for each property of the context.data
object. You can run the benchmark
yourself, but simple merges run at ~1,000,000 ops/sec and complex merges at
~400,000 ops/sec.
3.1.0 (2019-06-01)
FAQs
Vue.js util for intelligently merging data passed to functional components.
The npm package vue-functional-data-merge receives a total of 309,951 weekly downloads. As such, vue-functional-data-merge popularity was classified as popular.
We found that vue-functional-data-merge demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.