Architecture | Vue JS Application Without Build
Introduction
People would often say how Vue JS or React are simple, even trivial.
Well… I for one disagree ;-) They’re not simple. After all, they’re widely used to build massive, often mission-critical systems. There’s plenty to learn beyond these wildly optimistic Learn React in a Day courses. Their ecosystems are huge. Tooling is demanding. Documentation is extensive. It takes substantial effort to discover and understand best practices and efficient design patterns.
What’s their appeal then? For me, it’s their progressive character. Complexity is introduced into the project gradually, only when it becomes necessary. I can start with simple JavaScript, a few prerequisites and without a complex build setup. Then, as my needs grow, I start adding new concepts and learn how to use them. Things such as modules, components, routing, state management, state propagation, async code, reactivity, server-side rendering - they all eventually come into picture. But only when their time comes, and only when I’m ready for them!
Source code for the article can be found at https://bitbucket.org/letsdebugit/minimalistic-vue.
Simple tools for simple projects
When I begin a new project, it’s crucial that it’s simple to start. Cognitive load of this profession is already hard enough. I don’t need any more of it, unless truly necessary.
Equally important is that project setup stays simple, as long as the application remains simple. For many projects all that I will ever need is a little smart engine behind a web page. Something to wire up a photo gallery. Something to fetch updates from an external source and keep the UI in sync. Why would I introduce TypeScript and webpack just for that? But Vanilla JS comes at a significant cost. I love having things such as state management, reactivity and data bindings. They save so much time and they help building a consistent UI.
Luckily, this is possible with progressive web frameworks. In the example below I want to show how to introduce Vue JS and enjoy its powers in a simplest possible way.
Application Design
The example below is a tiny one-page web application. It has a header, content area and a footer. In the content area there’s a message and a button. When user clicks on the button, the message changes:
As a prudent programmer, I want to properly structure my application from the beginning. There are the following elements in the UI:
- header
- main area
- footer
I’d love to have each of these defined as a separate component. I’d like to keep their code in separate modules, easy to identify and work with.
In a typical Vue JS setup you’d use single-file .vue
components for that. This unfortunately requires a build process based on webpack, rollup etc. It turns out that you can get nearly the same experience without any build process! It maybe isn’s as comprehensive as the original deal, but it’s just fine for many simple scenarios. More important, it has none of complexities and dependencies introduced by a regular build process and CLI tools.
Project structure
The project is structured as follows:
index.html
index.js
index.css
header/
header.js
header.css
content/
content.js
content.css
footer/
footer.js
footer.css
Our logical UI components are clearly reflected in the physical structure of the project.
Bootstrapping
When browser loads index.html
, the following happens:
- Vue JS library is fetched from CDN repository at https://unpkg.com/vue
- Component stylesheets are fetched
- Application module is imported from
index.js
and executed
Notice how we use
<script type="module">
to tell the browser that we’re loading modern ES6 code with all the bells and whistles!
When index.js
is executed, it imports subsequent modules which contain our components:
- Content from
/content/content.js
- Header from
/header/header.js
- Footer from
/footer/footer.js
Components
The components are not much different from regular Vue JS single-file components. They can have all the features and functionality of a Vue JS component, such as:
- data
- props
- methods
- computed properties
- lifecycle events
- slots
- template with markup
- etc.
Because there is no build process, our components have to be put together in a different way. Modern JavaScript features will help us here. Instead of bundling we can simply import
the required dependencies wherever needed. After all those years of mindless bundling browsers finally know how to import
modules ;-) Then, instead of a <template>
block we will use JS template literals.
Component code is structured as follows:
const template = `
<div>
...
</div>
`
export default {
template,
data () {
},
computed: {
},
// etc.
}
The main application component is in the index.js
file. Its task is to assign custom HTML tags such as <app-header>
or <app-footer>
to all our components.
import Header from './header/header.js'
import Content from './content/content.js'
import Footer from './footer/footer.js'
const App = {
el: 'main',
components: {
'app-header': Header,
'app-content': Content,
'app-footer': Footer
}
}
window.addEventListener('load', () => {
new Vue(App)
})
These custom tags are then used to build our application UI in index.html
file. We ended up with a UI which is trivially simple to read:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Minimalistic Vue JS</title>
<link rel="stylesheet" href="index.css">
<link rel="stylesheet" href="header/header.css">
<link rel="stylesheet" href="content/content.css">
<link rel="stylesheet" href="footer/footer.css">
<script src="https://unpkg.com/vue">
</script>
<script src="index.js" type="module">
</script>
</head>
<body>
<main>
<app-header bg-color="#c5cae2">
</app-header>
<app-content>
</app-content>
<app-footer>
(c) Tomasz Waraksa, Dublin, Ireland
</app-footer>
</main>
</body>
</html>
Next Steps - Routing
Less-trivial application would typically have a couple of pages. Luckily we can use the standard Vue Router, which works just fine in our simple setup.
You can define pages just like other components, using the same recipe as described above. Then, instead of registering them as custom tags, link them to routes, for example:
import Home from './home/home.js'
import About from './about/about.js'
export default [
{
name: 'home',
path: '/',
component: Home
},
{
name: 'about',
path: '/about',
component: About
}
]
Fetch Vue Router library and add router placeholder in index.html
:
<head>
...
<script src="https://unpkg.com/vue-router">
</script>
</head>
<body>
...
<router-view>
</router-view>
...
</body>
Finally, initialize router together with the application in index.js
:
const router = new VueRouter({ routes })
const app = {
el: 'main',
router,
...
}
Now you can navigate between pages by entering the URL, using <router-link>
component or programmatically by calling Router.push()
method.
Summary
In the end, we have nearly the full power of Vue JS without any complexities of a build process. To deploy this application we would simply copy the files to a web server. Then just hope that our visitors will use a decent browser ;-)
References
The complete source code can be found at https://bitbucket.org/letsdebugit/minimalistic-vue. Feel free to clone and reuse this code. Any suggestions or questions are most welcome!
All credits and thanks go to the creators of the awesome Vue JS framework.