A deep dive in the Vue.js source code (#12): the generateComponentTrace function

the generateComponentTrace function

If you are just joining, this is the 12th post in a series going over the entire Vue.js source code line by line. In this post, we look at the generateComponentTrace function.

If you recall from our last post, the warn function calls a function named generateComponentTrace to set a variable named trace if a Vue instance is passed to the warning:

warn = function (msg, vm) {
var trace = vm ? generateComponentTrace(vm) : '';

In the last post, we glossed over the implementation details of generateComponentTrace to focus on the logic of the warn function. In this post, we loop back around to the generateComponentTrace function.

By default, generateComponentTrace is a function that does nothing:

function noop (a, b, c) {}
[. . . .]
var generateComponentTrace = (noop); // work around flow check

But if the environment is not a production environment, generateComponentTrace is set to a function that does some work:

if (process.env.NODE_ENV !== 'production') {
[. . . .]
generateComponentTrace = function (vm) {
[. . . .]
}
}

The function takes a parameter:

generateComponentTrace = function (vm) {
[. . . .]
}

And checks whether it is a Vue instance:

if (vm._isVue && vm.$parent) {
[. . . .]
} else {
return ("\n\n(found in " + (formatComponentName(vm)) + ")")
}

You will recall that the ._isVue property is set to true within the Vue.prototype._init method.

function initMixin (Vue) {
Vue.prototype._init = function (options) {
[. . . .]
// a flag to avoid this being observed
vm._isVue = true;
[. . . .]
}
}

generateComponentTrace next checks whether vm.$parent coerces to true.

Since this is our first introduction to vm.$parent, let’s take a closer look at it. Here, the Vue API is helpful. The API explains that vm.$parent is a property on a Vue instance and refers to the “The parent instance, if the current instance has one.” When parent is set as a property, the API explains that it:

Establishes a parent-child relationship between the two. The parent will be accessible as this.$parent for the child, and the child will be pushed into the parent’s $children array.

We will take an even deeper look at the parent-child relationship and how the parent becomes accessible as $parent when we discuss the initLifecycle function. For our purposes in this post, we can move forward just knowing that the if statement is checking whether the Vue instance has a $parent property.

If both conditions are met, generateComponentTrace initializes a variable tree and sets it to an empty array:

var tree = [];

And then sets a variable currentRecursiveSequence to 0:

var currentRecursiveSequence = 0;

The beginning of the while loop may appear a little odd:

while (vm) {
[. . . .]

How does the loop quit? How does vm ever become anything other than vm? Look at the end of the loop for the answer. At the end of each loop, the Vue instance is pushed in to the tree array and vm is reset to the parent of the current instance:

while (vm) {
[ . . . .]
tree.push(vm);
vm = vm.$parent;
}

In other words, the loop works recursively up through the Vue instances through their .$parent properties and exits when an instance does not have a .$parent property.

So let’s turn back to the start of the loop. The loop first checks whether the length of the tree array is greater than 0:

if (tree.length > 0) {
var last = tree[tree.length - 1];
if (last.constructor === vm.constructor) {
currentRecursiveSequence++;
vm = vm.$parent;
continue
} else if (currentRecursiveSequence > 0) {
tree[tree.length - 1] = [last, currentRecursiveSequence];
currentRecursiveSequence = 0;
}
}
tree.push(vm);
vm = vm.$parent;

If the length of the tree array is greater than 0, the variable last is set to the last element in the array. Since arrays in Javascript are zero based — meaning that the array index starts at 0 — the length of an array is always one more than the last element in an array. Thus, you have to set last to the element of the tree array at an index of the length of the array minus one:

var last = tree[tree.length - 1];

Next, we check whether last.constructor is strictly equal to vm.constructor:

if (last.constructor === vm.constructor) {
currentRecursiveSequence++;
vm = vm.$parent;
continue
} else if (currentRecursiveSequence > 0) {
tree[tree.length - 1] = [last, currentRecursiveSequence];
currentRecursiveSequence = 0;
}

If so, the currentRecursiveSequence variable is incremented:

currentRecursiveSequence++;

And the Vue instance is set to its parent property:

vm = vm.$parent;

And the continue statement “terminates execution of the statements in the current iteration of the current [] loop, and continues execution of the loop with the next iteration.”

continue

In other words, the following is not called after continue:

tree.push(vm);
vm = vm.$parent;

If last.constructor is not strictly equal to vm.constructor and currentRecursiveSequence is greater than 0, the last element of the tree is set to an array with two elements: last and currentRecursiveSequence. And then currentRecursiveSequence is set to 0:

} else if (currentRecursiveSequence > 0) {
tree[tree.length - 1] = [last, currentRecursiveSequence];
currentRecursiveSequence = 0;
}

Finally, the Vue instance is pushed onto the tree and — as discussed above — the Vue instance is reset to the Vue instance’s .$parent property:

tree.push(vm);
vm = vm.$parent;

And that is the end of the while loop. It runs until you reach a vm with no .$parent property.

Next, we hit a lengthy return statement.

We’ll start taking a look at that return statement in the next post.

Next Post: