Generically overriding component render functions in Vue.js

Besides from using templates, Vue.js components can be set up with a render function, allowing one to programmatically determine their output in a more advanced way. Combining this feature with the HOC pattern, it is thus possible to easily create "augmented" versions of any component in an app, which can come in handy in some situations.

For the sake of example, let's picture a scenario where we have a form, and want to display each of its fields twice: the regular version, and a disabled version, displaying a recommended value based on business rules.

The resulting HOC might look something like this:

import clone from "clone";
import getDisabledVersion from "./getDisabledVersion";

export default function withRecommendedValue(originalComponent) {
const component = clone(originalComponent); // deep-clone component to avoid reference errors
const originalRender = component.render;
const recommendedValue = /* fetch it from wherever */;

component.render = function(h) {
return h("div", {}, [
getDisabledVersion(originalRender.call(this, h), recommendedValue), // disabled doppelganger
originalRender.call(this, h) // original component
]);
};

return component;
}

originalRender.call(this, h) will give us the component's rendered virtual DOM tree as a JSON object, which can then be amended by our getDisabledVersion function, overriding its value and disabled state. The recursive nature of this function implies that it will possibly deal with multiple cases:

  • if our component only contains plain DOM nodes, we will be looking for form controls (i.e. input, select, or textarea) and update their attributes directly; other DOM nodes will be checked for children to call the function upon
  • on the other hand, if our component contains other components, we will rather be interested in their props; specific value handling/transformation that was handled by our own component, if any, will also have to be done here

And this is where the magic happens:

export default function getDisabledVersion(el, value) {
if (["input", "select", "textarea"].includes(el.tag)) {
// Form control node
el.data.domProps.value = value;
el.data.attrs.disabled = true;
} else if (el.componentOptions) {
// Component
// Do dirty things to value if relevant
el.componentOptions.propsData.value = value;
el.componentOptions.propsData.disabled = true;
} else if (Array.isArray(el.children)) {
// Something else, we need to go deeper
el.children = el.children.map(child => getDisabledVersion(child, value));
}

return el;
}

Finally, to use the HOC:

Vue.component("MyComponentWithRecommendedValue", withRecommendedValue(MyComponent));

Of course, with the upcoming uprising of version 3.0 and its composition API, this pattern might soon be a thing of the past; I'm eager to see how it will make this kind of voodoo trick easier (or not). Feel free to drop knowledge about it in the comments if you have some!

Sharing a Webpack vendor bundle across applications

If you find yourself maintaining standalone, yet related SPAs, and you would like your users to benefit from their browser's cache to download some third-party code they have in common only once, Webpack has you (pretty much) covered thanks to its DllPlugin, which is actually part of a duet along with DllReferencePlugin. To make a long story short, the former allows you to generate, through a separate build, a standalone bundle and an accompanying manifest file, whereas the latter takes these two as input and in consideration in the context of a full, regular build. Let's get started!

We will assume one of the apps will bear responsibility for building the common bundle. As I mentioned earlier, this implies creating a separate, specific Webpack config:

const webpack = require("webpack");
const path = require("path");

module.exports = {
entry: {
commonVendors: [/* list the names of your common dependencies here */]
},

output: {
path: path.join(__dirname, "vendor"),
filename: "[name].js",
publicPath: "/",
library: "Vendor"
},

plugins: [
new webpack.DllPlugin({
context: __dirname,
path: path.join(__dirname, "vendor", "manifest.json")
})
]
};

You might need extra stuff, like setting up loaders to support your framework's DSL, if any - I will leave that up to you. Let's build!

$ webpack --config path/to/specific/config.js

This should result in the creation of a vendor folder, containing commonVendors.js and manifest.json files. Now, on to our main config:

const webpack = require("webpack");

module.exports = {
// ...

plugins: [
// ...

new webpack.DllReferencePlugin({
context: __dirname,
manifest: require("./vendor/manifest.json"),
name: "Vendor" // this has to match output.library in the specific config
})
]
};

If you run your build as usual (and you ran the first one already), you should notice a decrease in your regular bundles' size! Of course, make sure to add a <script src="path/to/vendor/commonVendors.js"></script> tag to your HTML page.

OK, so we now have a separate bundle that we can use in this one application. How should we proceed to benefit from it in another build? Well, that is really up to you and your concrete use case: there is, to my knowledge, no perfect solution given it will always imply build coupling. Feel free to drop your knowledge in a comment below!

Just as a proof of concept, let's have it working in development though: have commonVendors.js and manifest.json available through a local server or, like me, just rely on the filesystem, and in your second app's Webpack config, make use of DllReferencePlugin as well:

const webpack = require("webpack");

module.exports = {
// ...

plugins: [
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require("../path/to/manifest.json"),
name: "Vendor"
})
]
};

Once again, you should see your JavaScript footprint decrease, and no lack of code if you correctly import the common bundle file in your app!

Mock current time with Moment.js or Day.js

When writing unit tests for JavaScript code relying on Moment.js or Day.js (a lightweight alternative to the former, with pretty much the same API) to handle date-related shenanigans, one might find themselves in need to mock their library of choice when its main function is used with no parameters, to obtain a representation of current time.

The mock we actually need to set up in such a case is actually a partial one: we want it to act as a proxy for parameterized calls, passing them down to the original implementation, while returning a fixed value otherwise. Thankfully, Jest grants us the possibility to do so fairly easily:

jest.mock("dayjs", () => jest.fn((...args) => jest.requireActual("dayjs")(
args.filter(arg => arg).length > 0 ? ...args : "1989-03-21"
)));

As you can see, the aforementioned original implementation is available from within jest.mock's scope through the use of jest.requireActual, allowing us to check if it was called with or without parameters, and pass a fixed value in the latter case.

Congratulations, you are now able to freeze time!

Recursive function binding in a JavaScript object

Despite the rise of ES6 and especially the sacrosanct fat arrow syntax, JavaScript developers might still find themselves in need for Function.prototype.bind in some edge cases; binding all functions in an object to the same this value regardless of depth is one of them.

The example I have in mind is the building of a Vue.js plugin such as this one:

function install(Vue) {
Vue.mixin({
beforeCreate() {
this.$myPlugin = {
aMethod() {
console.log(this);
},

yet: {
anotherMethod() {
console.log(this);
}
}
};
}
});
}

export default { install };

Fairly logically, setting it up through Vue.use(MyPlugin) and calling this.$myPlugin.aMethod and this.$myPlugin.yet.anotherMethod yields a reference to the plugin object itself twice, which might not be what you want depending on what the plugin is supposed to do.

In such a situation, we therefore need to write a recursive binding function, which luckily is not all that hard:

function recursiveBind(obj, that) {
return Object.keys(obj).reduce((acc, key) => {
switch (typeof obj[key]) {
case "object":
return { ...acc, [key]: recursiveBind(obj[key], that) }; // recurse

case "function":
return { ...acc, [key]: obj[key].bind(that) }; // bind
}

return { ...acc, [key]: obj[key] }; // leave untouched
}, {});
}

All that remains to do then is to wrap our plugin object declaration with it:

function install(Vue) {
Vue.mixin({
beforeCreate() {
this.$myPlugin = recursiveBind({
aMethod() {
console.log(this);
},

yet: {
anotherMethod() {
console.log(this);
}
}
}, this);
}
});
}

And this now correctly refers to our Vue instance! Please share your plugin tips in the comment section, if any!