This is part of a series explaining how to use VueJS to create a transplation-free client-side templating architecture. I'm not claiming this is The Best Way to create a UI, but it's a good thought experiment for minimizing ceremony.
- Part 1: VueJS introduction -- Progressive, observables, loops, & ifs
- Part 2: Components, Templates Types, and SPAs (unedited)
- Part 3: Using Render Functions & HTML Files (to come)
NOTE: I haven't reread/edited this one yet, I'm afraid. Bbl.
In Part 1, we had all the tools we absolutely required to make a single page app (SPA) with VueJS, but the HTML was a mess. Worse, if it got any more complicated and the different "views" shared any UI, we could tell it'd quickly stop being DRY.
The solution to this problem is to employ reusable components. Here's a quick visual from the "Intro to VueJS" video from Vue's home page.
Concentrate mostly on the stuff in the middle... we might want to use that Product Image component in a few different places, for instance. With components, we can define it once, and then reuse all over the place.
Now there are a number of different ways to create components, but, honestly, only a few that really make sense in a practical work environment. The first that we're going to review -- and the one most sites, including Vue's own documentaions start with -- really doesn't make great sense: inline text. But let's do it anyway for kicks. The reason will make more sense later...
Let's take that "cats" markup from Part 1 and componentize it.
Old markup:
<!-- ==== FROM HERE ==== -->
<div v-if="activeUi === 'cats'">
<h3>Cats</h3>
<ul>
<li v-for="cat in cats">
{{ cat }}
</li>
</ul>
</div>
<!-- ===== TO HERE ===== -->
<div v-else-if="activeUi === 'dogs'">
<h3>Dogs</h3>
<ol>
<li v-for="dog in dogs">
{{ dog }}
</li>
</ol>
</div>
<div v-else>
<h3>Neither dogs nor cats</h3>
<ul>
<li v-for="x in others">
{{x.name}}, a {{x.type}}
<!-- And note that we can embed ad infinitum, natch -->
<ul v-if="x.type === 'rooster'">
<li>"That's a joke, I say, that's a joke son!"</li>
</ul>
</li>
</ul>
</div>
Vue's component model is pretty conventional, and awfully familiar if you've used React. Here, there's no real business logic. We just need to get that the cats
data into the component and then loop over it. And we do that with props, which is just a fancy way of saying properties that we bind in the component's declaration in our HTML.
Here the above cats-specific markup as a component. I've got it in a new file marked components.js
so that it's not mixing with my core business logic, but you can drop it anywhere you want, as long as it's executed before your main Vue app instantiation. Recall that I've got my main Vue call in a document.addEventListener("DOMContentLoaded", function() {...});
wrapper, so it's not going until the rest of the page is read and rendered.
/*global Vue, window */
(function () {
"use strict";
Vue.component("cats", {
props: ['store'],
template: '<div><h3>Cats</h3><ul><li v-for="cat in store">{{ cat }}</li></ul></div>'
});
}());
And here's how the HTML changes:
NOTE: Check the
:
beforestore
, which tells us to bind the object represented by"cats"
rather than treating it as a literal string.
<!-- ==== FROM HERE ==== -->
<cats v-if="activeUi === 'cats'" :store="cats"></cats>
<!-- ===== TO HERE ===== -->
<div v-else-if="activeUi === 'dogs'">
<h3>Dogs</h3>
<!-- ... and so forth... ->
That's certainly a lot cleaner, right? If we componentize it all, making similar dogs
and others
components, we exchange all the markup from the first code dump, above, to this:
<cats v-if="activeUi === 'cats'" :store="cats"></cats>
<dogs v-else-if="activeUi === 'dogs'" :store="dogs"></dogs>
<others v-else :store="others"></others>
A few things to note:
- Our HTML looks great. No clutter. If we want to see what happens with cats, we know to look at that specific component.
- The
template
string can include Vue markup. You see{{ cat }}
there. So that's cool -- we can get as nesty as we want. - The template string is okayish for the small markup we have, but boy, that'd be messy for lots of markup. It's not even easy to read as it stands!
I bet we can do better, right? Let's see if there's a way to keep the HTML in its own file, where we're not worried about newlines or surrounding values in single quotes, and escaping single quotes in the template, and...
Pros and Cons for VueJS Template Types
There's a decent intro to the different ways you can create templates for components in Vue here on medium from Anthony Gore. Let's run through them and list some pros and cons.
- String
- Template literal
- X-Templates
- Inline
- Render functions
- JSX
- Single page components
1. String
The first is what we just did. template
is literally a string. As Gore says, "I think we can all agree that templates in a string are quite incomprehensible. This method doesn’t have much going for it other than the wide browser support." It's really not scalable, and heaven help anyone debugging or editing a large HTML value within quotes.
2. Template Literals
The second is to use es6's "template literals". This is a new syntax which allows us to define multiline strings using backticks instead of quotes to delimit strings. That's great, and would improve our cats
component definition quite a bit...
Vue.component("cats", {
props: ['store'],
template: `
<div>
<h3>Cats</h3>
<ul>
<li v-for="cat in store">{{ cat }}</li>
</ul>
</div>`});
That's a big step up, but it's not IE happy at all. For me, at least, that's still an issue. There's also spotty escape sequence support, so it's not really HTML ready. And strangely, they don't have any compatibility right now (20180411) for Safari on iOS. ??
3. X-Templates (script tags)
X-Templates are when you use the trick that you can drop HTML into a script
tag with an id
and then link to it from your component definition. This allows you to embed all your components' html into a single file if you wanted, which is an improvement.
<script type="text/x-template" id="dogs-template">
<div>
<h3>Dogs</h3>
<ol>
<li v-for="dog in store">
{{ dog }}
</li>
</ol>
</div>
</script>
That, of course, can go anywhere you want in your html, or in a script include. This isn't an end of the world, and is actually how we used to do templates in KnockoutJS. The only thing I hate is that it feels a little kludge and that it's harder to get your editor set up to highlight the HTML code as HTML inside of script tags.
The component is very clean, however.
Vue.component("dogs", {
props: ['store'],
template: "#dogs-template"
});
4. Inline templates :^P<######
Inline templates are a horrible idea, and I can't even find a place on Vue's website that claims it's supported. I'm not saying it's not supported. I'm saying it's a horrible idea. Inline templating is when you put the template between the component's tags in your "parent" markup. Here's Gore's example:
<my-checkbox inline-template>
<div class="checkbox-wrapper" @click="check">
<div :class="{ checkbox: true, checked: checked }"></div>
<div class="title"></div>
</div>
</my-checkbox>
OMGWTFBBQ! The whole purpose of components is that you can then reuse them with minimal overhead. Now I get it -- perhaps there are times when you want different markup for each use of the component. Maybe. But if something's ever duplicated, you're stuck cut and pasting markup, and you've lost your DRYness. Yuck.
5. Render Function: Spoilers
I'm going to come back to render functions. *wink wink wink*
6. JSX & 7. Single file components.
The final two involve transpilation. You can use JSX in Vue if you want, just like you're back in React-land. Insert Kerriganian "WHY?!!?!"
The second is actually what you probably came to Vue for in the first place, single file components. I really do like the idea of single file components. They contain the template, javascript, and, get this, scoped CSS all in the same file. It sounds wonderful.
Here's why it's not, from vuejs.org:
For Users New to Module Build Systems in JavaScript
With
.vue
components, we’re entering the realm of advanced JavaScript applications. That means learning to use a few additional tools if you haven’t already:
Node Package Manager (NPM): Read the Getting Started guide through section 10: Uninstalling global packages.
Modern JavaScript with ES2015/16: Read through Babel’s Learn ES2015 guide. You don’t have to memorize every feature right now, but keep this page as a reference you can come back to.
After you’ve taken a day to dive into these resources, we recommend checking out the webpack template. Follow the instructions and you should have a Vue project with
.vue
components, ES2015, and hot-reloading in no time!To learn more about Webpack itself, check out their official docs and Webpack Academy. In Webpack, each file can be transformed by a “loader” before being included in the bundle, and Vue offers the vue-loader plugin to translate single-file (
.vue
) components.
NOTE: Either of these involve the gateway drug to that horrible cesspool of build scripts and maintenance that I'm trying to avoid for you. It's also a, well, JavaScript builds are not exactly a broken window; they're more like a stained glass window. Once you have a single piece of stained glass, someone on the team is going to add eighteen more build steps^H^H^H^H^H^H pieces of stained glass, and suddenly you have a priceless stained glass window that looks like a snowflake, if you get my [wait for it...] drift.
Again, this is how everyone does JavaScript development these days, but I'd warn you against it. At the very least, let's see how bad things get if we avoid transpiliation, if just as a thought experiment. The series is called "Templating without Transpilation", after all.
Tune in next time in Part 3, when we finally get where we're going, using, you guessed, it, render functions, and have a solid architecture that allows templating without transpilation.