Migrating Vue2 App without sourcecode
November 2022
Or: How I ended up recreating a small subset of VueJS 2
Context
Last day I was looking for my old web-blog prototypes an found old web-app that I developed back in 2018 in VueJS 2 to test the framework. The main problem with it was that most of its content was lost (including the sources) and only bundled version were pushed into my backup system…
A snapshot of my blog website, “Misete!”
Analysis of the bundled code
Hopefully It’s not lost, I have the source in the map right ? Let’s open the dev tools and read the JavaScript, it should feel familiar and …
The compiled VueJS code, barely readable
Well not really, at least there’s some variable name kept at compile time and the URL / Slugs are conserved too. Good to know, let’s see what we can do with it.
After opening VS Code and setting pretty-print on, I got the following pattern that showed up, driving me to think that VueJS 2 have some function that render node depending of their type.
With some formatting, we start to see some interesting functions
I then started a new about:blank
window in my browser and started to thinker around the script. Enjoyer of REPL-like experience, I did naïve script that define _c
and _vm
in the window, copied the script and executed, then looked at its output and fixed it until I got some basic system working.
Building a Virtual DOM
Let’s keep things simple, first eval the render function and build a Virtual DOM structure from it, then flush everything to console. Taking a look at how the functions are commonly used, I guessed a lot about VueJS 2 internals but couldn’t reproduce most of it for the sake of simplicity.
So from my understanding :
_c
means “create element”, and look similar to the$createElement
. Let’s set an alias and declare it._l
is a loop function, like Svelte’s{#each }
block. Let’s keep that in mind and put it aside for now$t
is commonly used in vue-i18n to translate strings. It takes a string as key
Each of the _vm
Virtual DOM functions work with a Virtual Node (VNode
) instance, that may be presented the following :
var VNode = function (tag, data, child, text, elm, context) {
// this.val setters
};
I then created some simple builder for every function like so :
var _self = {
_c: (tag, data, child) => {
// create text VNode
return new VNode(tag, data, child);
// console.log(`_v ${val}`);
},
};
You may notice that I defined them right in the window component, no wrapper or anything. It may be considered bad practice for production code but it’s super easy to thinker faster and patch on the fly that way.
Let’s test it with a basic render function :
function render() {
var _vm = this,
_c = _vm._self._c;
return _c("div", [
_c("img", {
attrs: {
alt: "Vue logo",
src: "https://maleplate.dev/svelte-welcome.png",
width: "25%",
},
}),
_c("h1", [_vm._v(_vm._s(_vm.hello))]),
_c("p", [_vm._v("Some paragraph test")]),
]);
}
And there we go ! A nice Virtual Dom tree structure :
Getting Real from Virtual
Since we have a super simple VueJS 2 template parser, let’s de-virtualize it and create a real HTML DOM from it !
I wrote a simple helper to print the Virtual Dom tree :
function print(c, tab = "") {
if (!c.tag) return;
console.log(`${tab}<${c.tag}>`);
if (c.children) {
for (let child of c.children) {
print(child, tab + " ");
}
}
console.log(`${tab}</${c.tag}>`);
}
That quickly got this nice HTML-like tree structure printed from it :
<div>
<img>
</img>
<h1>
</h1>
<p>
</p>
</div>
The idea here is basically to chain document.createElement(vnode.tag)
and recreate a VueJS-looking HTML from it. Since it’s a long task I won’t go into details here but the basic idea is to check the Virtual Node type and data, then loop over its child and build them recursively, to finally appendChild
to the base element and attach it to the dom.
It worked great, except for the _vm.hello
variable that wasn’t defined. At first I thought defining it (since that’s basically what VueJS does in the initialize part or your component) but it became hard once you got 10 more of them.
Thankfully, the JS Proxy element saved me, mixed with the Function.call property saved me the manual task and produced decent mapping.
Finally, the long task was to get the data
part of the Virtual Node to render correctly on HTML, but setAttribute
with VueJS-like name did a great job.
Here’s the original test code from VueJs 2 I used to benchmark my script :
<template>
<div>
<img
alt="Vue logo"
src="https://maleplate.dev/svelte-welcome.png"
width="25%"
/>
<h1>{{ hello }}</h1>
<p>Some paragraph test</p>
</div>
</template>
And the generated HTML code :
The generator did great
And the webpage looked visually the same (including the global CSS)
Looking good ! Let’s now try in on real code
My VueJS blog, in HTML
What will the code do against much complex code ? Well, at first it crashed a few time because of missing _vm
helper but thanks to the quick REPL experience of the blank page, it was easy to fix on the fly.
Seeing the page getting back on the fly was an awesome feel, as the memories from the days I discovered VueJS got back and brought me back to 2018 for a while.
Then I got my Virtual Dom structure :
That’s a great tree structure for such a small code !
And the generated code, looked a lot like what I have imagined :
Generated HTML code for the homepage posts list
I finally got to a fully working Virtual DOM structure after 3 hours of fixes and researches. Some may say recreating it in Svelte or Astro would have took me 10 minutes, but now I have a powerful tool to parse any (hopefully ?) VueJS 2 built website and generate a near-VueJS template looking HTML.
It’s a great refresh for my blog that was dead for so long, and it’ll get back to my website soon !