DEV Community

Guillaume Martigny
Guillaume Martigny

Posted on • Edited on

How I write web apps in 2021 ?

Content

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.

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 and how to use NPM.

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
Enter fullscreen mode Exit fullscreen mode

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

Vue

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

$ npm install vue
Enter fullscreen mode Exit fullscreen mode

Bundler

I like the modular template syntaxe 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
Enter fullscreen mode Exit fullscreen mode

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

$ npm install vue-loader vue-template-compiler
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

// 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(),
    ],
};
Enter fullscreen mode Exit fullscreen mode

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

$ webpack
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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).

// 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",
});
Enter fullscreen mode Exit fullscreen mode

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

$ webpack --mode=development --watch
Enter fullscreen mode Exit fullscreen mode

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).

<!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>
Enter fullscreen mode Exit fullscreen mode

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

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.

<template>
    <h1>Home</h1>
</template>

<script>
export default {
    name: "Home",
};
</script>
Enter fullscreen mode Exit fullscreen mode

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.

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/>",
});
Enter fullscreen mode Exit fullscreen mode

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.

<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>
Enter fullscreen mode Exit fullscreen mode

And use it in Home.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>
Enter fullscreen mode Exit fullscreen mode

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.

<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>
Enter fullscreen mode Exit fullscreen mode
<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>
Enter fullscreen mode Exit fullscreen mode

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, I can use mounted function to execute code when the view appears on screen.

<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>
Enter fullscreen mode Exit fullscreen mode

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.

<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>
Enter fullscreen mode Exit fullscreen mode

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.

npm install vuetify
Enter fullscreen mode Exit fullscreen mode

Really few changes are needed to activate it in index.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/>",
});
Enter fullscreen mode Exit fullscreen mode

Then I can use it everywhere in my application (here in Film.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 -->
Enter fullscreen mode Exit fullscreen mode

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, I'm able to freely host, deploy and serve with a few clicks. All I need is for the project to be on Github.

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.

// 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",
        },
    },
    // ...
};
Enter fullscreen mode Exit fullscreen mode

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

Top comments (5)

Collapse
 
shalvah profile image
Shalvah

Didn't plan on reading this because I don't do frontend, but somehow I got sucked in.😅 This is a really great post on how to get from scratch to app. Nice! Made Webpack and co feel less intimidating.

Also, Vue is awesome.🤘

Collapse
 
gmartigny profile image
Guillaume Martigny

Thanks for the kind words, glad you liked it.

Collapse
 
shayelbaz1 profile image
Shay Elbaz

Why do you use Vue?

Collapse
 
gmartigny profile image
Guillaume Martigny

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.

For my personal taste, Vue is the best front-end framework on the market. The template component syntaxe, the two-ways variable binding, the great documentation, the OOP style, the ease of installation and use ...

Collapse
 
shayelbaz1 profile image
Shay Elbaz

Acctally Im happy to hear that, as a Vue developer.partytimes.herokuapp.com/#/
this is my last project.