In-depth object prop validation in Vue.js

Vue.js offers a simple yet effective prop validation API:

export default {
props: {
config: {
type: Object,
required: true
}
}
};

For any given prop, you can specify the expected type, and whether the prop is required or has a default value. Doing so is actually best practice, just like defining data as a function in your components.

Regarding Object props, however, there is no built-in mechanism allowing one to validate the object's keys or structure; the closest thing we have is the ability to pass in a custom validation function, so let's build upon that!

The most simple implementation could be along the lines of the following:

const configKeys = ["some", "keys", "to", "check"];

export default {
props: {
config: {
type: Object,
required: true,
validator: config => configKeys.every(key => key in config)
}
}
};

That pretty much does it! A more thorough implementation could support deep key checking with a dotted notation, using this package for example.

But let's go further! If you find yourself using this pattern on a regular basis, why would you not write a mixin - actually, a mixin factory - to make it easier?

/**
* @param {String} propName
* @param {String[]} keys
*
* @return {Object}
*/
function getObjectPropMixin(propName, keys) {
return {
[propName]: {
type: Object,
required: true,
validator: value => keys.every(key => key in value)
}
};
}

export default {
mixins: [getObjectPropMixin("config", ["some", "keys", "to", "check"])]
};

You could even use it multiple times in the same component if you have more than one Object prop to check; or better yet:

/**
* @param {Object} propDefs
*
* @return {Object}
*/
function getObjectPropsMixin(propDefs) {
return Object.keys(propDefs).reduce((props, key) => {
return {
...props,
[key]: {
type: Object,
required: true,
validator: value => propDefs[key].every(propKey => propKey in value)
}
}
}, {});
}

export default {
mixins: [getObjectPropsMixin({
config: ["some", "keys", "to", "check"],
anotherObject: ["and", "here", "are", "more", "dummy", "values"]
})]
};

Using such stuff gives us the confidence to reference keys in Object props directly, without defensively checking for their presence in our code. If you have other ways to deal with this kind of thing, please let me know!

CLI scripts for ES6-powered packages with ESM

If you are developing your own front-end JavaScript packages, chances are you use ES6 syntax in your codebase, especially the long-awaited module system. Browser compatibility is pretty much painless thanks to Babel, but things can get harder if you want your package to expose a CLI script (to generate code or whatever) that is meant to be ran through Node.js directly.

ESM to the rescue! Add esm to your project's package.json as a dependency (or peerDependency if you want to leave its installation up to your package's consumer), and your users will then be able to run your script with node -r esm node_modules/path/to/script.js.

The only detail to pay attention to is to account for the difference between ES6's module system and CommonJS's:

const someES6Module = require("./path/to/module").default;   // explicitly require default export if that is what you need
const namedExport = require("./path/to/module").namedExport; // named exports are to be referenced this way

// Or just use destructuring:
const { default: someES6Module, namedExport } = require("./path/to/module");

Making a Vue.js application usable from the outside: the widget pattern

Hello there! It's been a while since I posted anything, but today I'd like to take a moment to tell you about a simple yet quite efficient pattern we've been using at my current job regarding Vue.js applications.

As with any front-end framework, Vue.js isn't really opinionated regarding how you're supposed to inject configuration into your SPA, assuming this configuration comes from your back-end service. A common pattern in this era of API-centric services is to dedicate a JSON endpoint to configuration that your JavaScript code will fetch immediately upon boot. This allows us to keep all back-end considerations aside, but has several disadvantages as well:

  • limited flexibility if the network or your back-end itself fail (the more you can provide for an offline and/or degraded experience, the better)
  • somewhat extraneous network round-trip for something you could have provided locally to begin with
  • your JS code still needs to know which URL to request, which is already configuration; how do you provide that?

For all these reasons, we have began to experiment with a little something we like to call the widget pattern. Nothing crazy here: we essentially wrap the new Vue declaration inside a build function, which is then called from public/index.html:

<body>
<div id="app"></div>
<script>
document.addEventListener("DOMContentLoaded", function() {
window.App.build("#app", { /* ... */ });
});
</script>
</body>

We then use its second argument in src/main.js to inject config into our app, which we can variabilize depending on the environment if we need to, by swapping wildcards with environment variables from Docker for example:

import Vue from "vue";
import App from "./components/App";

export default App; // more on that later

export function build(el, config) {
new Vue({
el,
render: h => h(App, props: { /* ... */}),
});
}

All we need to do to make this work is to expose our root module in vue.config.js:

module.exports = {
configureWebpack: {
output: {
library: "App",
libraryTarget: "umd"
}
}
};

This gives us the flexibility we need, and forces us to reflect on our app's root component's input, as its props are used to receive the aforementioned configuration values. We thus also made an habit of exporting this root component as default, which means we can use our app on its own as before or use it as a "widget" (hence the "pattern" name) in a larger Vue.js codebase or a legacy application.

If you're using something similar or have something to say about this way of doing things, use the comment section below!

Handle peer dependencies with NPM 3

The latest major version of Node.js's package manager introduces a breaking change regarding the peerDependencies section of the package.json file. With NPM 2, these dependencies were installed in the root node_modules folder; this is NPM 3's default behaviour, and peerDependencies are no longer fetched by npm install. Instead, the tool will simply warn you if these dependencies are unmet. But what if you relied on this feature and have to support both versions?

Looking for an answer to this question, I stumbled upon this article that proposes a simple solution: duplicate peerDependencies as dependencies, which will result in installing the right stuff at the right place no matter the NPM version in use. This works, but has a few drawbacks, the most important in my eyes being the obvious code configuration duplication, which often rhymes with maintenance pain.

I therefore put up a Bash workaround, that will play nicely with Jenkins jobs and other install scripts:

# Install dependencies once, just to fetch packages with peerDependencies
npm install

# Check for current NPM version
NPM_MAJOR_VERSION=`npm -v | cut -d \. -f 1`

if [ "$NPM_MAJOR_VERSION" == "3" ]
then
# Turn peerDependencies into dependencies for relevant packages
sed -i 's/peerDependencies/dependencies/g' ./node_modules/package-with-peer-deps/package.json

# Install again, for real this time
npm install
fi

# Do stuff...

Kinda dirty, but it works with no need to think about it. Feel free to discuss this solution and propose something better in the comments!

Note: using npm shrinkwrap also seems to work around this once dependencies have been correctly installed at least once, which could cancel the need to perform the above trick on a regular basis.

Handling an universal breadcrumb in a flux application with react-breadcrumbs

If you are using react-router, the odds are that you have already tried react-breadcrumbs to handle breadcrumbs based on your routing configuration, which can come in handy.

But what if your application is not a SPA and part of your routing (and your breadcrumb) happens server-side? This is what we will achieve today with some dirty DOM magic.

First, let's consider our HTML page:

<!DOCTYPE html>
<html>

<head>
<!-- ... -->
</head>

<body>
<div class="breadcrumb"><!-- server-side breadcrumb --></div>
<div id="react"><!-- React app will be rendered here --></div>
<!-- scripts, etc. -->
</body>

And here is our root component:

'use strict';

let React = require('react'),
Breadcrumb = require('react-breadcrumbs');

module.exports = React.createClass({
/**
* @return {Object}
*/
render: function() {
return (
<div className="container">
<Breadcrumb routes={this.props.routes} />
{this.props.children}
</div>
);
}
});

The Breadcrumb JSX tag will be rendered as our client-side breadcrumb; what we rather want is to have both breadcrumbs concatenated in our div.breadcrumb.

Let's make some additions to the root component:

'use strict';

let React = require('react'),
Breadcrumb = require('react-breadcrumbs');

module.exports = React.createClass({
/**
* Maintains breadcrumb up-to-date with dynamic, react-router-powered part
*
* The actual breadcrumb is outside React's scope, so we basically render
* a hidden breadcrumb with the aforementioned part and copy its contents
* at the end of it after wiping it clean, all through DOM manipulation.
*/
updateBreadcrumb: function() {
let breadcrumb = document.querySelector('.breadcrumb');

// We use React's own specific HTML attributes to clear the breadcrumb from previous additions
Array.prototype.slice.call(document.querySelectorAll('.breadcrumb > [data-reactid]')).forEach(function(toRemove) {
breadcrumb.removeChild(toRemove);
}, 0);

breadcrumb.innerHTML += document.querySelector('.breadcrumbHolder').innerHTML;
},

componentDidUpdate: function() {
// Trigger breadcrumb update everytime our route changes
this.updateBreadcrumb();
},

/**
* @return {Object}
*/
render: function() {
return (
<div className="container">
{/* Hide client-side breadcrumb and prevent it from explicitly displaying missing segments, if any */}
<Breadcrumb routes={this.props.routes} customClass="breadcrumbHolder hide" displayMissing={false} />
{this.props.children}
</div>
);
}
});

And it works! This is a total hack, but React's very own philosophy prevents us from more interaction with a preexistant DOM, at least for now. Stay tuned!