How I write web apps in 2021 ?
Tags: javascript, tutorial, vue, webpack
Published: 2021-02-10 11:21:46

# Content

  - [Introduction](#introduction)
    - [Disclaimer](#disclaimer)
  - [Languages](#languages)
  - [Installation](#installation)
    - [Vue](#vue)
    - [Bundler](#bundler)
    - [Optionals](#optionals)
  - [Development](#development)
    - [Adding views and components](#adding-views-and-components)
      - [Views](#views)
      - [Components](#components)
    - [Dynamic page](#dynamic-page)
      - [Fetch from API](#fetch-from-api)
      - [User editing](#user-editing)
    - [Component libraries](#component-libraries)
  - [Deployment](#deployment)
  - [Troubleshooting](#troubleshooting)
  - [FAQ](#faq)


# Introduction

In this article, I'm going to present you step by step what tools I use.
You can follow along, but it's more important to understand "why I am doing this" rather than "what I am doing".
A good advice is, try to do the same step alongside me in this tutorial. Then, try to change a few bigger and bigger things. Finally, at the end, you should be able to do everything again on your own.

You can find all the produced code over at [the Github repository](https://github.com/GMartigny/tuto2021).


## Disclaimer

First of all, and this is really important, all of this is my own bias opinion on development. We all have our unique way at looking at things.
Web development is a huge and complex topic. This post is not meant to describe the easiest or quickest methods.
However, this is the methods I grew up preferring (for reasons I'll get to), and the one I'm most capable of explaining in details.

From here, I assume you have basic Javascript and Vue understanding. I'm also not going to detail how to [install Node.js](https://nodejs.org/en/download/) and [how to use NPM](https://docs.npmjs.com/).


# Languages

With that out of the way, let's start with languages.
I have worked with Javascript for about 10 years. It has a lot of detractors, but it was and still is the language I enjoy the most.
It's easy to use, has one of the largest community and can power huge application.

Of course, I'm also writing everything in English. Although it's not my mother tongue, it's recognize as the international language.


# Installation

Node.js is installed on my computed, so I'll use NPM to install all my JS dependencies.
The first thing I always do when starting a new project is

    $ npm init

This will create the `package.json` file.
Then I manually create a `readme.md` and `.gitignore` file as well as a `src` directory that will be useful later.

![Preview of my project file system](https://dev-to-uploads.s3.amazonaws.com/i/lpstd2l5dxpp8n0crehk.png)


## Vue

I like Vue and that's what I mostly use. The installation is straightforward

    $ npm install vue


## Bundler

I like the [modular template syntaxe](https://vuejs.org/v2/guide/single-file-components.html) that Vue offer. However, this is not native JS that a browser can understand.
So, it needs to be transformed before it can be used.

I use Webpack for that. The installation is not as simple because we need a lot more modules.
First, let's start with Webpack itself and its CLI interface

    $ npm install webpack webpack-cli

Then, I need to add the plugin that handle Vue files with its compiler

    $ npm install vue-loader vue-template-compiler

Finally, chances are that I'm going to write CSS, so I need another pair of plugins to handle CSS code

    $ npm install css-loader style-loader

Now, I need to configure Webpack. This is the least fun part, but we need to understand this step to solve possible future issues.
Webpack can be configured with a file named `webpack.config.js`, so let's create it.

Here is the bare minimum. We'll come back later if we need to expand it.

```js
// Get the vue-loader plugin
const VueLoaderPlugin = require("vue-loader/lib/plugin");

// This is what the file exports
module.exports = {
    // My entry point
    entry: "./src/index.js",
    module: {
        rules: [
            // All Vue files use the vue-loader
            {
                test: /\.vue$/,
                loader: "vue-loader",
            },
            // All CSS files use css than style loaders
            {
                test: /\.css$/,
                use: [
                    "style-loader",
                    "css-loader"
                ]
            },
        ],
    },
    plugins: [
        // Register the vue-loader plugin
        new VueLoaderPlugin(),
    ],
};
```

With all that out of the way, I just need to run in my terminal

    $ webpack

to see my project being entirely compacted and minified. This will fail for now, don't worry.


## Optionals

These tools are out of scope for this article. Maybe I'll go in more details in the next one.

I always use Eslint to check for potential mistakes in my code.
In order to use it with my personal configuration I run

    $ npm install eslint eslint-plugin-vue @gmartigny/eslint-config

I try to test my code to catch regression and be sure that I cover most use cases. I use AVA for the test and NYC for the code coverage.

    $ npm install ava nyc


# Development

That's already a lot of steps, and I haven't even write a single line of code. All of that looks like a lot, but trust me, it will make you go faster in the future.

The most attentives around you would remember that in my Webpack configuration, the entry file was `./src/index.js`. So, let's start there.
I create an `index.js` file in `src` and add a few lines of code to call Vue ([with ESM](https://vuejs.org/v2/guide/installation.html#Explanation-of-Different-Builds)).

```js
// Import Vue, I prefer the ESM syntaxe
import Vue from "vue/dist/vue.esm.js";

// Create a new Vue instance targeted at the element with the id "app"
new Vue({
    el: "#app",
});
```

With this bare-bones JS file, I can safely run

    $ webpack --mode=development --watch

to trigger Webpack in development mode (slower but more descriptive on errors) with watch (will rebuild every time I change the code).
This will create a new `main.js` file within a `dist` directory. It's the file that my final users will use.

I now create a `index.html` file (usually in a `public` directory, but it doesn't matter).

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Test</title>
</head>
<body>
<!-- My target element -->
<main id="app"></main>
<!-- The JS file from webpack -->
<script src="../dist/main.js"></script>
</body>
</html>
```

Opening this file in your browser will show nothing as expected, but this is all working. This is the state of my project so far.

![Preview of my project file system](https://dev-to-uploads.s3.amazonaws.com/i/707iwd57o7c0mufrgsc8.png)


## Adding views and components

Your Vue files should be split between views (an individual screen, eg: Menu, About ...) and components (composing your views, eg: Button, Footer ...)
Both works the same, but don't have the same concerns. So let's add two directories (`views` and `components`) in `src` to sort them out.


### Views

Let's begin by creating a new view. This is going to be the homepage, so I'll call the file `Home.vue`.

> I used a capital letter in the file name to show that it's a class as in other OOP language like Java.

```vue
<template>
    <h1>Home</h1>
</template>

<script>
export default {
    name: "Home",
};
</script>
```

In order to go into that view, I have to tell my Vue instance to render it. In the `index.js` file, I'll add the necessary lines.

```js
import Vue from "vue/dist/vue.esm.js";
// Import the view
import Home from "./views/Home.vue";

new Vue({
    el: "#app",
    // Declare it as a components I'll use (I know, views are treated as components by Vue)
    components: {
        Home,
    },
    // Render the view
    template: "<Home/>",
});
```

In order to have more views, you need navigation between the views, so you need `vue-router`. We're not going to talk about it for now.


### Components

Imagine I want to have a simple card (title + text) for each film I want to see, I don't want to duplicate the code for each cards. A good rule is DRY (Don't Repeat Yourself).
If you write something more than twice, it should be factorized in one place.

Again, I create a new file called `Film.vue` in the `components` directory.

```vue
<template>
    <div class="film">
        <h2>Title</h2>
        <p>Text</p>
    </div>
</template>

<script>
export default {
    name: "Film",
};
</script>

<!-- scoped because I don't want to interfere with any other component -->
<style scoped>
.film {
    border: 1px solid blue;
}
</style>
```

And use it in `Home.vue`.

```vue
<template>
    <div>
        <!-- Use the component -->
        <Film />
        <Film />
        <Film />
    </div>
</template>

<script>
// Import the component
import Film from "../components/Film.vue";

export default {
    name: "Home",
    components: {
        // Declare the component
        Film,
    },
};
</script>
```

As you should already see, I have 3 cards with the same title and text.
Which is not what I want to do.
If I add properties to my card component and write data in my home view, it will allow me to define value for each individual card.

```vue
<template>
    <div class="film">
        <!-- Use properties here -->
        <h2>{{ title }}</h2>
        <p>{{ text }}</p>
    </div>
</template>

<script>
export default {
    name: "Film",
    // Properties list declaration
    props: ["title", "text"],
};
</script>

<style scoped>
.film {
    border: 1px solid blue;
}
</style>
```

```vue
<template>
    <div>
        <!-- Loop through my data -->
        <Film v-for="(film, index) in films" :key="index"
              :title="film.title" :text="film.text"/>
    </div>
</template>

<script>
import Film from "../components/Film.vue";

export default {
    name: "Home",
    components: {
        Film,
    },
    // data should be a function
    data () {
        // that return a set of values
        return {
            films: [
                {
                    title: "Alien",
                    text: "It follows the crew of the commercial space tug Nostromo, who encounter the eponymous Alien, an aggressive and deadly extraterrestrial set loose on the ship.",
                },
                {
                    title: "Interstellar",
                    text: "In a dystopian future where humanity is struggling to survive, it follows a group of astronauts who travel through a wormhole near Saturn in search of a new home for mankind.",
                },
                {
                    title: "Reservoir Dogs",
                    text: "Diamond thieves whose planned heist of a jewelry store goes terribly wrong.",
                },
            ],
        };
    },
};
</script>
```

With that in place, any changes applied to my data will be reflected on screen.


## Dynamic page

I can, for example, fetch data from an API or allow the user to edit the page (or both 😉).


### Fetch from API

First, I'll fetch my data from a mock API online. In order to do that, I start by emptying the data array.
Then, according to the [Vue lifecycle](https://vuejs.org/v2/guide/instance.html#Lifecycle-Diagram), I can use `mounted` function to execute code when the view appears on screen.

```vue
<template>
    <!-- ... -->
</template>

<script>
import Film from "../components/Film.vue";

export default {
    name: "Home",
    components: {
        Film,
    },
    data () {
        return {
            // Emtpy film list
            films: [],
        };
    },
    async mounted () {
        // Fetch from mock API
        const response = await fetch("https://mock-film-api-t0jk5mabvwnt.runkit.sh/");
        if (response.ok) {
            // Load result into films list
            this.films = await response.json();
        }
    },
};
</script>
```


### User editing

In the same way, I can allow users to add new film to the list. A small HTML form that push a new entry when submitted will see the modifications reflected on the view.

```vue
<template>
    <v-app>
        <Film v-for="(film, index) in films" :key="index"
              :title="film.title" :text="film.text"/>
        <!-- Form that will call addFilm when submitted -->
        <form @submit="addFilm">
            <div>
                <label for="title">Title</label>
                <!-- v-model link the input value to a variable -->
                <input type="text" v-model="inputTitle" id="title">
            </div>
            <div>
                <label for="text">Text</label>
                <input type="text" v-model="inputText" id="text">
            </div>
            <button type="submit">Add</button>
        </form>
    </v-app>
</template>

<script>
import Film from "../components/Film.vue";

export default {
    name: "Home",
    components: {
        Film,
    },
    data () {
        return {
            films: [],
            // Holds the value of each input
            inputTitle: "",
            inputText: "",
        };
    },
    async mounted () {
        // ...
    },
    methods: {
        // New method
        addFilm (event) {
            // Stop the form from reloading the page (default behavior)
            event.preventDefault();
            // Add a new film to the list
            this.films.push({
                title: this.inputTitle,
                text: this.inputText,
            });
            // Clear out input fields
            this.inputTitle = "";
            this.inputText = "";
        },
    },
};
</script>
```

This will of course not save anything online, and changes made will be lost when the page is reloaded.
You could improve this example by sending a request to a server that save the inputs in a database.


## Component libraries

I'm lazy. Being an efficient developer often means being lazy.
Instead of creating all my components, I can use already existing libraries of components. That way, I can focus more on content instead of how to correctly design a datepicker.

Since I use Vue, I choose a Vue compatible library, [Vuetify](https://vuetifyjs.com/en/getting-started/installation/#webpack-install).

    npm install vuetify

Really few changes are needed to activate it in `index.js`.

```js
import Vue from "vue/dist/vue.esm.js";
// Get Vuetify and its CSS
import Vuetify from "vuetify";
import "vuetify/dist/vuetify.min.css";

import Home from "./views/Home.vue";

// Prepare the usage of vuetify
Vue.use(Vuetify);
const vuetify = new Vuetify();

new Vue({
    // Pass the instance to Vue
    vuetify,
    el: "#app",
    components: {
        Home,
    },
    template: "<Home/>",
});
```

Then I can use it everywhere in my application (here in `Film.vue`).

```vue
<template>
    <!-- All Vuetify components are prefixed with "v-" -->
    <v-col cols="12">
        <v-card shaped>
            <v-card-title>{{ title }}</v-card-title>
            <v-card-text>{{ text }}</v-card-text>
        </v-card>
    </v-col>
</template>

<script>
    // ...
</script>

<!-- I don't need my crappy style anymore -->
```


# Deployment

One of my favorite recent discovery is serverless. Basically, as long as your application is stateless (always return the same result with the same parameters), you don't need to have a complex, always running server. By leveraging the power of caching and ressource sharing you can reduce your server to almost nothing.

Using [Vercel](https://vercel.com/), I'm able to freely host, deploy and serve with a few clicks. All I need is for the project to be on [Github](https://github.com/).


# Troubleshooting

> You are using the runtime-only build of Vue where the template compiler is not available.

Your import of Vue is wrong. If you remember, there are many ways to import Vue. By default, `import "vue"` will call the `vue.runtime.common.js` file.
In my code here, I'm using the ESM with template (so I need the `vue.esm.js`).


> [Vuetify] Multiple instances of Vue detected / this.$vuetify is undefined

Your application and Vuetify didn't import the "same" Vue. As explain above, it's important to import the right Vue for your usage.
A nice solution to this is to create an alias in webpack.

```js
// In webpack.config.js
module.exports = {
    // This will tell all your code to use the same Vue.
    resolve: {
        alias: {
            vue$: "vue/dist/vue.esm.js",
        },
    },
    // ...
};
```


# FAQ

> Why do you don't use CLI to setup your app ? (like `vue-cli` or `create-react-app`)

While these CLI are great ways to quickstart a project, they slow you down in the long run. They all use the same technologies I presented you today, but in a complex and confusing way.
Using `create-react-app` install about 2000 packages. If something goes wrong, debugging that much code is not an easy task.
I've always preferred taking the longer road, but at the end of the day, understand what's going on.


> Why don't you need to import each Vuetify components when you use them ?

When I do `Vue.use(Vuetify);` in the `index.js`, it activates it in the whole project.
It's possible to import only Vuetify components when you use them. But this requires a bit more work that is out of scope for this tutorial.


> Why don't you use **X** instead of **Y** ?

I'm used to it. I'm sure you'll find better alternative to any tools or methods I describe above. But I know them.
At the end of the day, it's more important to deliver than endlessly learn new technologies.


> What are my alternatives ?

Vue:
  - React
  - Angular
  - Svelte

Vuetify:
  - Material-UI
  - Any CSS frameworks:
    - Bootstrap
    - Bulma
    - Tailwind

Webpack:
  - Rollup
  - Skypack

Vercel:
  - Netlify
  - Heroku