Handle circular NPM package dependencies with Webpack

If you maintain your own set of JavaScript libraries and publish them on NPM, you might find yourself in a situation where a package A depends on a package B, which in turn depends on A itself. Although you should generally strive to avoid such an ordeal, one can always imagine some edge case where this is a valid way of implementing things. The main problem is that it means each package will require a version of itself installed in node_modules which, on top of being semantically ugly, can make for really painful upgrades, as bumping the version of B in A and then releasing A should be followed by updating to this new version in B, and so forth... so what can we do to avoid this?

The quickest way to break the cycle is to remove A from B's dependencies (or the other way around, depending on what makes the most sense) and add it to its peerDependencies instead. It basically tells your package consumers that the latest compatible version of A should be installed alongside B for everything to work properly, without installing it directly: this transfers responsibility of maintaining the installed version of A onto them. You might also want to add A to B's devDependencies if its presence is necessary for tests to run properly, or anything else related to its maintenance. This should ease the pain within your dependency tree!

Finally, when developing A (which still explicitly depends on B), you might need to set things up so import calls targetting A resolve to your local code, the twin dependency not being around anymore. Doing so depends on your specific build setup, but it is pretty easy to do with Webpack thanks to the resolve part of its configuration:

{
resolve: {
// tell Webpack to look for third-party code in A's root directory before node_modules
modules: [__dirname, "node_modules"],

alias: {
"A": __dirname // tell it to resolve A to this same directory
}
},

// ...
}

This example assumes your entry point (typically index.js) lives in A's root directory; if it rather is is src/, for example, you will want to use path.resolve(__dirname, "src") instead.

Feel free to let me know of a better way to overcome such pitfalls when they cannot be avoided, if you know any!

Overcoming race conditions when using a global event bus in Vue.js

To ease communication between components which are not directly related, and when using a state manager such as Vuex seems overkill, Vue.js developers' go-to pattern is the global event bus: while it feels kind of hacky, it most certainly is easy to both use and reason about.

Or is it though? Depending on your specific use case, there is virtually no guarantee the event listeners will be bound before the events are emitted. Wouldn't it be nice if we had some sort of mechanism that could keep the early-emitted values in memory and apply them upon binding the listeners? Why do I keep asking questions when you know damn well the answer lies right below?

import Vue from "vue";

const bus = new Vue();
const lastValues = {};
const originalEmit = bus.$emit.bind(bus);
const originalOn = bus.$on.bind(bus);

bus.$emit = function(name, value) {
lastValues[name] = value;
originalEmit(name, value);
};

bus.$on = function(name, callback) {
if (lastValues[name] !== undefined) {
callback(lastValues[name]);
}

originalOn(name, callback);
};

export default bus;

This is, too, sort of hacky, but it most certainly does the trick, remains lighter than Vuex, and is conceptually not too far away from immediately-applied watchers one can define in their components. Let me know your thoughts!

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!