Getting Started with WebPack

Here's a good article on Webpack

Here are the starter files for this activity. The steps below show you how to set up all these files, so you don't neccessarily need the starter files.

Creating the starter siles for the Project with NPM

Create a folder named npm-webpack.

Open a terminal in the project-folder (npm-webpack) folder and initialize an npm project by running this command:

npm init -y

Note that the -y will create the package.json file with default settings so that we don't have to awnser a bunch of questions to set everything up.

Starter files

The project folder (npm-weback) should have these items in it:

public/
  index.html
src/
  assets/
    styles.css (this file should be empty)
  js/
    functions-module.js
  index.js

Note that index.html is in the 'public' folder, and that index.js is the 'src' folder, NOT in the 'js' folder.

The index.html file should have this code in it:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <script src="bundle.js"></script>
        <title>Document</title>
    </head>
    <body>
        <h1>Hello World</h1>
        <p>Note that his file is linking to <b>bundle.js</b>.</p>
    </body>
</html>

The file functions-modules.js in the js/ folder should have this code in it:

export function someFunction(){
    alert("someFunction() is running..");
}

export function someOtherFunction(){
    alert("someOtherFunction() is running..");
}

The index.js file should have this code:

import {someFunction} from './js/functions-module'
import './assets/styles.css'
someFunction();

Setup Webpack

Now install two webpack packages by running this command:

npm install webpack webpack-cli --save-dev

In the npm-webpack folder, create a file named webpack.config.js and add this to it:

const path = require('path')

module.exports = {
    mode: 'development',
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'public'),
        filename: 'bundle.js'
    },
    devtool:false,
}

This file specifies that index.js is the entry point, meaning that it's the file that will import all of our javascript modules.

The output object tells webpack to 'bundle' all of our javascript code into a file named bundle.js. and to put it in the public folder (specified in the path property of the output object.

The mode property is set to 'development', we could change this to 'production' to compress our code, but we'll leave it as is for now.

By setting the devtool property to false, we are disabling source maps, which can make your compiled code very difficul to read! It you want to experiment with this, set devtool to 'source-map'

Open a terminal in the project folder (npm-webpack) and run this command:

npx webpack

You should see a bundle.js file in the public folder.

At this point you should be able to open the index.html page in the browser and see your JavaScript code run.

This is the most basic way of setting up a Webpack project.

Optional

A module that exports a JavaScript class

In the js folder add a file named class-module.js and put this code in it:

class SomeClass{

	someMethod(){
		alert("Some method is running")
	}

	someOtherMethod(){
		alert("Some other method is running")
	}

}

export default SomeClass

Note that this file defines a JavaScript class. Also note that the class is being exported (at the bottom). This will allow us to import it into other files.

Go back to index.js and add this code (put it just under the last import statement):

import SomeClass from './js/class-module'

const obj = new SomeClass();
obj.someMethod();

We import SomeClass, then instantiate it and invoke a method.

Now run webpack npx webpack and then reload the index.html page in the browser

More on working a module that exports functions

Lately there's been a trend toward creating modules that consist of functions rather than a class. This allows you to import only the functions you need to use (when you import a class it will come with all of it's methods even if you don't need them all)

Here is a slightly different way to write the code in functions-module. With this approach you do not use the export statement until the last line. And this export statment declares that you are exporting an object that has the two functions in it (this version will work exactly the same as the one above, but I'm showing it to you in case you see it out in other people's code):

function someFunction(){
	alert("someFunction() is running..");
}

function someOtherFunction(){
	alert("someOtherFunction() is running..");
}

export {someFunction, someOtherFunction}

Note that we only imported one of the functions from functions-module.js

Go ahead and run npx webpack to compile the JavaScript code into bundle.js.

Set Up Webpack to Watch for Changes

It's gets old to have to run webpack when you make a change. We can add an option when we run webpack to have it watch for changes. Run this command:

npx webpack --watch

Now webpack will watch for changes in the src folder and automatically compile the files into public folder.

Install a Dev Server

We can automate our development process one step further by not only compiling our files automatically, but also launching the project on a web server too.

Install the webpack dev server package by running this command:

npm install --save-dev webpack-dev-server

By default, webpack will look in the public folder for an index.html file to launch in the server.

Now add a 'serve' script to the package.json file (just under the 'build' script we added earlier)

"serve": "webpack serve --port 8080 && webpack --watch"

Run this command in the terminal (from the project folder):

npm run serve

Now you can work on your project and webpack will automatically watch for changes and reload the server!

Note

By default, webpack will look for an index.html file in the 'public' folder. But if you wanted to have the dev server look for an index file in another folder you could add a 'devServer' key to the module.exports object in the web.config.js file like so:

devServer:{
    contentBase:'./some-other-folder',
    open: true, // opens the page in a browser
    hot: true, // refreshes the page when a file changes
    watchContentBase: true // refreshes when the contentBase folder has a change
}

Loading Assets

Websites usually make use of assets such as stylesheets, images, videos, etc. By default, webpack requires you to incorporate assets into your project by importing them into .js files (which seems really strange if you're used to building websites the old-fashioned way).

In order to incorporate various assets into your project, you'll need to install a 'loader' package, as we'll see in a moment.

Importing StyleSheets

Install this package:

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

Webpack has many 'loader' packages that help you accomplish tasks. After you install a loader, you usually need to configure it in the webpack.config.js file by adding a 'module' object and a 'rules' array within it.

Add this code to the webpack.config.js file (put it inside the module.exports object):

module: {
	rules: [
  		{
    		test: /\.css$/i,
    		use: ['style-loader', 'css-loader'],
  		},
	],
}

The entire contents of webpack.config.js should now look like this:

const path = require('path')

module.exports = {
	mode: 'development',
	entry: './src/js/index.js',
	output: {
		path: path.resolve(__dirname, 'public'),
		filename: 'bundle.js'
	},
	devtool:false,
    module: {
        rules: [
              {
                test: /\.css$/i,
                use: ['style-loader', 'css-loader'],
              },
        ],
    }
}

The value of the 'test' property is a regular expression that tells webpack to apply the two loaders to any .css files that are imported into index.js.

The two loaders that we just installed will allow us to incorporate our CSS code via JavaScript (again, which may feel very strange if you are used to old-fashioned web development).

Add this import to index.js:

import '../assets/styles.css'

Note that the public folder does not include any stylesheets. The CSS code actually got compiled into bundle.js. Have a look through that file and look at how the CSS code in src/styles.css was compiled into JavaScript code.

Importing Images

We don't need to install any loader packages to use images in our project because the loader we need is now baked into the webpack package. As of Webpack 5 the loader we need for this is now bundled into the webpack package.

But we do need to modify webpack.config.js to work with images. Add this object to the 'rules' array in webpack.config.js:

{
    test: /\.(png|svg|jpg|jpeg|gif)$/i,
    type: 'asset/resource',
},

The complete webpack.config.js file now looks like this:

const path = require('path')

module.exports = {
	mode: 'development',
	entry: './src/js/index.js',
	output: {
		path: path.resolve(__dirname, 'public'),
		filename: 'bundle.js'
	},
	devtool:false,
    module: {
        rules: [
            {
                test: /\.css$/i,
                use: ['style-loader', 'css-loader'],
            },
            {
                test: /\.(png|svg|jpg|jpeg|gif)$/i,
                type: 'asset/resource',
            },
        ],
    }
}

NOTE that the objects that you add to the 'rules' array in webpack.config.js run in reverse order, so the last rules object runs before the first one.

Now we can import images into our javascript files. Let's create a function that returns a component, sort of like a React componet. This will be a simple one that displays a paragraph and an image. Create a file named component.js inside the js folder and add this code to it:

import Koala from '../assets/images/Koala.jpg'

export default function component(){
    const div = document.createElement("div");
    const p = document.createElement("p");
    p.textContent = "This is some component in your app"
    const img = new Image();
    img.src = Koala;
    div.append(p)
    div.append(img);
    return div
}

Now import the component into index.js like so:

import component from './js/component' 

And then add this code to index.js to place an instance of the component onto the page:

document.body.append(component());

When the server reloads, you should see the component rendered on the page.

Creating a dist folder

When we are ready to deploy our web app into production we'll want all the files to go into a 'dist' folder. Then we should be able to simply copy the contents of the dist folder onto the live server.

To do this, we'll create a separate webpack file named webpack.dist.config.js. Go ahead and create the file and put this content into it:

const path = require('path')

module.exports = {
	mode: 'development',
	entry: './src/js/index.js',
	output: {
		path: path.resolve(__dirname, 'dist'),  // changed this to 'dist'
		filename: 'bundle.js',
        clean: true                             // added this
	},
	devtool:false,
    module: {
        rules: [
            {
                test: /\.css$/i,
                use: ['style-loader', 'css-loader'],
            },
            {
                test: /\.(png|svg|jpg|jpeg|gif)$/i,
                type: 'asset/resource',
            },
        ],
    }
}

Note that we've only made two changes to this file after copying it from web.config.js. We changed the output path to go to the 'dist' folder, which will be created when run webpack. And we added the 'clean' option, which will just clean out the dist folder each time we run webpack.

Now let's configure a script in the package.json file run webpack using the new configuration file. Add this to the 'scripts' object in package.json:

"build": "webpack --config webpack.dist.config.js"

Go ahead and run the script by entering:

npm run build

Look in the dist folder and you'll see bundle.js, and our image file (although it has been renamed to use a content hash - which is a way to prevent browsers from storing files in the cache).

But what you won't see is the index.html file. I'm not sure why webpack doesn't have an easier way to copy files, but we have to install a plugin to help us with that.

Run this command to install the HTML Webpack plugin:

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

Update the webpack.dist.config.js to look like this:

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin'); // Import the HTMLWebpack plugin

module.exports = {
	mode: 'development',
	entry: './src/js/index.js',
	output: {
		path: path.resolve(__dirname, 'dist'), 
		filename: 'bundle.js',
        clean: true
	},
	devtool:false,
    module: {
        rules: [
            {
                test: /\.css$/i,
                use: ['style-loader', 'css-loader'],
            },
            {
                test: /\.(png|svg|jpg|jpeg|gif)$/i,
                type: 'asset/resource',
            },
        ],
    },
    plugins:[
	    new HtmlWebpackPlugin({
            template: './public/index.html',  // the file to copy
            filename: './index.html',   // the destination of the file in the dist folder
            inject: false   // prevents script injection
        })
	]
}

Note that we import the HTMLWebpack plugin at the top of the file. We also added a 'plugins' array to the module.exports object. When you install a plugin in Webpack, you can configure it by adding an object to this array. The HtmlWebpackPlguin() constructor return such an object. The template property specifies the file to copy. The filename property specifies the destionation withn the output older (remember that our output folder is set to 'dist'). If you don't set the inject property to false, then you'll notice that bundle.js will be included twice in the destination file. Strange, but whatever!

Configuring the Project for Sample Code

Webpack is primarily designed to work with single-page web applications. But we can set up our site to use multiple .html pages. This is better for when we want to create code samples.