Ahmed Ashraf

Intro

As a Backend engineer fully focused on backend technologies and tools. it's a bit hard to be updated with what is happening in the FrontEnd world. Modern Javascript development was a nightmare for me as I really want to use all these modern powerful stuff. but every time a big chunk of errors explode in my terminal window. so I decided to learn how to build a js project with my favorite frontend tools and I thought writing a blog post about it would be cool to also help others.

Creating the Skeleton: Webpack 4

At first, you need to understand why we will use Webpack and what values does it have.

Modern Javascript work now gets done by just outputting a single file contains everything.

consider it a binary file where everything gets compiled into one file.

so in development, you write code into many files and once done you compile it to one file and run it on production.

Webpack flow

as you can see in the picture from webpack website. on the left, you have the source code (many files) and once you compile them with webpack it outputs the compiled files of your code.

of course, you can decide how you want to build (compile) your project. check webpack docs for more.

Let's start the project by running

mkdir webpack-2020 && cd webpack-2020

the run npm init and follow instructions

Press ^C at any time to quit.

package name: (webpack-2020)
version: (1.0.0)
description:
entry point: (index.js)
test command:
git repository:
keywords:
author:
license: (ISC)
About to write to /Users/ahmed.ashraf/ash-projects/webpack-2020/package.json:

{
  "name": "webpack-2020",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}


Is this OK? (yes)

Next, let's install webpack

npm install webpack webpack-cli --save-dev

Webpack since v4 supports env vars and doesn't enforce configuration file. but config files are always good so we will use it to easy our work and will be easy for you to understand.

create webpack.config.js file with the following content

// webpack v4
const path = require('path');

module.exports = {
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].js'
    },
    target: "web",
};

so the config exports an object that leads webpack on how to compile our source code.

Webpack takes an entry point .js file and process it then storing the output into ./dist directory as entry file name [name].js = main.js

the target key helps webpack to optimize the output. we will discuss it later.

but now we need to create src/index.js file to see the output.

mkdir src && echo 'console.log("Hello World!");' > src/index.js

now run webpack command to build our project

webpack --mode development

the output will be like

Hash: e082fb89ab4f6d516592
Version: webpack 4.41.5
Time: 60ms
Built at: 15/01/2020 20:50:46
  Asset     Size  Chunks             Chunk Names
main.js  3.8 KiB    main  [emitted]  main
Entrypoint main = main.js
[./src/index.js] 29 bytes {main} [built]

and if you check the dist directory you will find the main.js file.

Now we need an html file to serve our js file. web pack doesn't support that by default. but luckily it supports plugins. so all we need is the webpack html plugin that can serve our html file from ./src directory to our ./dist directory

Let's install HtmlWebpackPlugin

npm install --save-dev html-webpack-plugin

then let's modify webpack.config.js and add these lines

plugins: [
  new HtmlWebpackPlugin({
    template: './src/index.html',
    filename: 'index.html'
  })
]

it's easy to read, right? we just told the webpack to use HtmlWebpackPlugin with ./src/index.html to output into ./dist/index.html

let's create our index.html in ./src file with the following content

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Webpack 2020</title>
</head>
<body>
    Hello World
</body>
</html>

now make sure your webpack file matches the following content

// webpack v4
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].js'
    },
    target: "web",
    plugins: [
        new HtmlWebpackPlugin({
            template: './src/index.html',
            filename: 'index.html'
        })
    ],
};

notice the require of the html-webpack-plugin in the third line

const HtmlWebpackPlugin = require('html-webpack-plugin');

now run the webpack command

webpack --mode development

now the ./dist/index.html will be like below

index.html content

notice how to automatically injected our main.js before closing the body.

now open the index.html in your browser and check your console

index.html browser

File hashes

if you try to change the console message in ./src/index.js and run webpack command the browser would display Hello World. because the browser cached our main.js file.

so to solve this problem and have a fresh file when the code changes with every build to tell the browser we have a new code you need to read. we use hashes for the file. so instead of main.js it would be main.abc123def.js and every time we build the value main.[HASH-CODE].js will change

webpack supports hashing, all you just need is to change the output file name to include [contenthash]

output: {
  path: path.resolve(__dirname, 'dist'),
  filename: '[name].[contenthash].js'
},

now the full webpack.config.js content will be

// webpack v4
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].[contenthash].js'
    },
    target: "web",
    plugins: [
        new HtmlWebpackPlugin({
            template: './src/index.html',
            filename: 'index.html'
        })
    ],
};

now run webpack --mode development and the output should be like below

hashed bundles

Clean builds

Now every time you build the project you get newly hashed files but the old files/hashes still live in the dist directory. the solution is very simple we need a way to remove the dist directory before every time we run build. and we are fortunate enough there is a webpack plugin for it

Let's install clean-webpack-plugin

npm install --save-dev clean-webpack-plugin

Now you know since it's a plugin. all we just need is to import it and it to plugins in webpack.config.js. do it your self and check the next step

The full webpack.config.js content will be like

// webpack v4
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].[contenthash].js'
    },
    target: "web",
    plugins: [
        new HtmlWebpackPlugin({
            template: './src/index.html',
            filename: 'index.html'
        }),
        new CleanWebpackPlugin(),
    ],
};

SASS & Tailwind

Let's jump to the next step and add some colors to our webpage. let's create a sass file ./src/styles.scss with the following content

body {
  background-color:black;
  color:white;
}

now the way of how to import this in our development process is not like the old way. we will go with CSS-in-JS approach.

so all you just need is to import the scss file in our ./src/index.js like below

import './styles.scss'

console.log("Hello World!");

but how can webpack understand the scss to process its content? nope, it's not a plugin this time :P it's Modules. webpack has a cool set of modules that supports a lot of front-end stuff.

Let's install node-sass, sass-loader, css-loader and style-loader

npm install style-loader css-loader sass-loader node-sass --save-dev

then add the following block to webpack.config.js

module: {
    rules: [
    {
        test: /\.(s*)css$/,
        use: ['style-loader', 'sass-loader']
    }
  ]
},

as you can see we help webpack to process our files through modules processors. the block tells webpack that for any .css, .sass and .scss use style-loader, css-loader and sass-loader to process the file

now the full webpack.config.js should be like

// webpack v4
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].[contenthash].js'
    },
    target: "web",
    plugins: [
        new HtmlWebpackPlugin({
            template: './src/index.html',
            filename: 'index.html'
        }),
        new CleanWebpackPlugin(),
    ],
    module: {
        rules: [
            {
                test: /\.(s*)css$/,
                use: ['style-loader', 'sass-loader']
            }
        ]
    },
};

run webpack --mode development and open the ./dis/index.html in the browser

SCSS Compiled

TailwindCSS

Let's see how can we add tailwindcss to our project. from the documentation the steps are very easy

npm install --save tailwindcss

then in ./src/styles.scss let replace the content with following 3 lines

@import "tailwindcss/base";

@import "tailwindcss/components";

@import "tailwindcss/utilities";

right now we have the proper setup for tailwindcss but we need post-css to transform tailwind to css

npm install --save postcss postcss-loader autoprefixer

now create a file in the root directory with name postcss.config.js

module.exports = {
    plugins: [
        require('tailwindcss'),
        require('autoprefixer'),
    ]
}

then in webpack.config.js let's add postcss to our css module

module: {
        rules: [
            {
                test: /\.(s*)css$/,
                use: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader']
            }
        ]
    },

again your full webpack config should be like following

// webpack v4
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].[contenthash].js'
    },
    target: "web",
    plugins: [
        new HtmlWebpackPlugin({
            template: './src/index.html',
            filename: 'index.html'
        }),
        new CleanWebpackPlugin(),
    ],
    module: {
        rules: [
            {
                test: /\.(s*)css$/,
                use: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader']
            }
        ]
    },
};

now before we build let's change the content of ./src/index.html to following tailwind nice page

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Webpack 2020</title>
</head>
<body class="bg-gray-300">
    <div class="flex justify-center mt-12">
        <div class="flex items-center bg-green-500 text-white text-sm font-bold px-12 py-3" role="alert">
            <svg class="fill-current w-4 h-4 mr-2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
                <path
                    d="M12.432 0c1.34 0 2.01.912 2.01 1.957 0 1.305-1.164 2.512-2.679 2.512-1.269 0-2.009-.75-1.974-1.99C9.789 1.436 10.67 0 12.432 0zM8.309 20c-1.058 0-1.833-.652-1.093-3.524l1.214-5.092c.211-.814.246-1.141 0-1.141-.317 0-1.689.562-2.502 1.117l-.528-.88c2.572-2.186 5.531-3.467 6.801-3.467 1.057 0 1.233 1.273.705 3.23l-1.391 5.352c-.246.945-.141 1.271.106 1.271.317 0 1.357-.392 2.379-1.207l.6.814C12.098 19.02 9.365 20 8.309 20z">
                </path>
            </svg>
            <p>Hello Friend</p>
        </div>
    </div>
</body>
</html>

then run webpack --mode development and open the browser

index-with-tailwind

VueJs

It's time to add more js cool stuff and use VueJS

Let's install the vue pacakges for webpack

npm install -P vue
npm install --save-dev vue-loader vue-template-compiler vue-style-loader

then we need to add vue-loader to webpack modules

{
  test: /\.vue$/,
  use: ['vue-loader']
}

also we need to add vue-style-loader to .c*ss rule

{
  test: /\.(s*)css$/,
  use: ['style-loader','vue-style-loader', 'css-loader', 'postcss-loader', 'sass-loader']
},

then we need to add VueLoaderPlugin to webpack plugins

new VueLoaderPlugin(),

The content of the webpack.config.js should be like following

// webpack v4
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const { VueLoaderPlugin } = require('vue-loader');

module.exports = {
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].[contenthash].js'
    },
    target: "web",
    plugins: [
        new HtmlWebpackPlugin({
            template: './src/index.html',
            filename: 'index.html'
        }),
        new CleanWebpackPlugin(),
        new VueLoaderPlugin(),
    ],
    module: {
        rules: [
            {
                test: /\.(s*)css$/,
                use: ['style-loader','vue-style-loader', 'css-loader', 'postcss-loader', 'sass-loader']
            },
            {
                test: /\.vue$/,
                use: ['vue-loader']
            }
        ]
    },
};

Now we need to change our index.js file to include Vue and our first component.

change ./src/index.html like following

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Webpack 2020</title>
</head>
<body class="bg-gray-300">
    <div id="app"></div>
</body>
</html>

we just removed the content and added a new div with #app id

then change ./src/index.js like following

import './styles.scss';
import Vue from 'vue';
import HomePage from './home.vue';

const app = new Vue({
    render: (createElement) => createElement(HomePage)
}).$mount('#app')

The second and the third line we imported Vue and our first HomePage compontent.

then we initiated the Vue app and mounted #app element to be our start point for Vue.

create ./src/home.vue with the following content

<template>
    <div class="flex justify-center mt-12">
        <div class="flex items-center bg-green-500 text-white text-sm font-bold px-12 py-3" role="alert">
            <svg class="fill-current w-4 h-4 mr-2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
                <path
                    d="M12.432 0c1.34 0 2.01.912 2.01 1.957 0 1.305-1.164 2.512-2.679 2.512-1.269 0-2.009-.75-1.974-1.99C9.789 1.436 10.67 0 12.432 0zM8.309 20c-1.058 0-1.833-.652-1.093-3.524l1.214-5.092c.211-.814.246-1.141 0-1.141-.317 0-1.689.562-2.502 1.117l-.528-.88c2.572-2.186 5.531-3.467 6.801-3.467 1.057 0 1.233 1.273.705 3.23l-1.391 5.352c-.246.945-.141 1.271.106 1.271.317 0 1.357-.392 2.379-1.207l.6.814C12.098 19.02 9.365 20 8.309 20z">
                </path>
            </svg>
            <p>{{ message }}</p>
        </div>
    </div>
</template>

<script>
export default {
    name: 'HomePage',
    data() {
        return {
            message: 'Hello Vue!',
        }
    }
}
</script>

and now you are ready. run webpack --mode development and open the ./dist/index.html in the browser

Index with Vue and Tailwind

TypeScript

TypeScript allows writing powerful oop apps. where you have classes and interfaces. also, it has static types (optional). and all your .ts code compiles to plain JavaScript.

Let's see how we can install and use it by running the following commands

npm install typescript ts-loader --save-dev

now add ts module to your webpack.config.js

{
  test: /\.tsx?$/,
  loader: 'ts-loader',
  exclude: /node_modules/,
  options: {
    appendTsSuffixTo: [/\.vue$/],
  }
},

then add resolve

resolve: {
  extensions: ['.ts', '.js', '.vue'],
},

then create tsconfig.json file

{
    "compilerOptions": {
        "module": "commonjs",
        "target": "es5",
        "sourceMap": true,
    },
    "exclude": [
        "node_modules"
    ]
}

your webpack.config.js should be like following

// webpack v4
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const { VueLoaderPlugin } = require('vue-loader');

module.exports = {
    entry: './src/index.ts',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].[contenthash].js'
    },
    target: "web",
    plugins: [
        new HtmlWebpackPlugin({
            template: './src/index.html',
            filename: 'index.html'
        }),
        new CleanWebpackPlugin(),
        new VueLoaderPlugin(),
    ],
    module: {
        rules: [
            {
                test: /\.(s*)css$/,
                use: ['style-loader','vue-style-loader', 'css-loader', 'postcss-loader', 'sass-loader']
            },
            {
                test: /\.vue$/,
                use: ['vue-loader']
            },
            {
                test: /\.tsx?$/,
                loader: 'ts-loader',
                exclude: /node_modules/,
                options: {
                    appendTsSuffixTo: [/\.vue$/],
                }
            },
        ],
    },
    resolve: {
        extensions: ['.ts', '.js', '.vue'],
    },
};

Look how it became so easy for you to read a webpack config file.

let's rename ./src/index.js to ./src/index.ts and change it content to type-script like following

import './styles.scss';
import Vue from 'vue';
import HomePage from './home.vue';

export class App {
    constructor(selector: string){
        new Vue({
            render: (createElement) => createElement(HomePage)
        }).$mount(selector)
    }
}

const app = new App('#app');

we created a class App that takes an element selector in constructor then we initialize The Vue app.

one thing to do is to change the ./home.vue component to be compatible with typescript like following

<template>
    <div class="flex justify-center mt-12">
        <div class="flex items-center bg-green-500 text-white text-sm font-bold px-12 py-3" role="alert">
            <svg class="fill-current w-4 h-4 mr-2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
                <path
                    d="M12.432 0c1.34 0 2.01.912 2.01 1.957 0 1.305-1.164 2.512-2.679 2.512-1.269 0-2.009-.75-1.974-1.99C9.789 1.436 10.67 0 12.432 0zM8.309 20c-1.058 0-1.833-.652-1.093-3.524l1.214-5.092c.211-.814.246-1.141 0-1.141-.317 0-1.689.562-2.502 1.117l-.528-.88c2.572-2.186 5.531-3.467 6.801-3.467 1.057 0 1.233 1.273.705 3.23l-1.391 5.352c-.246.945-.141 1.271.106 1.271.317 0 1.357-.392 2.379-1.207l.6.814C12.098 19.02 9.365 20 8.309 20z">
                </path>
            </svg>
            <p>{{ message }}</p>
        </div>
    </div>
</template>

<script lang="ts">
import Vue from "vue";
export default Vue.extend({
    name: 'HomePage',
    data() {
        return {
            message: 'Hello Vue from TypeScript!',
        }
    }
});
</script>

we just added lang="ts" attribute to the script and used Vue.extend for our component.

now run webpack --mode development and open your browser

Home page in vue, tailwind, and typescirpt

Source

I've uploaded the project on Github https://github.com/ahmedash95/js-webpack-2020. Enjoy ;)

Conclusion

I hope this article helped you to understand some of the webpack features and how to use it for your js projects.

also, open your ./webpack.config.js file and try to read it. you will find it so easy to understand and you know what every line means in this file.

if you find anything wrong in the article please correct me in the comments. also, if you've find it useful please share it with others :).

at

16 Jan 2020

Want to be updated with Laravel and other fun related stuff I'm doing. Just Subscribe