Webpack configuration

First of all, we want to define entry point by configuring the entry property in webpack.comfig.js file. We want to tell Webpack to use exactly one entry point for each page. This is possible by passing object with defined entryChunkName key and the path to js file as a value:

module.exports = {

entry: {

'index' : './src/index.js',

'products/product-1': './src/pages/products/product-1.js',

'contact' : './src/pages/contact.js'

}

};

Obviously, we don’t want to hard code it like above because whenever we add a new page, this configuration has to be updated. The desired solution is to find all .js files in the “pages” directory and generate an object with entry points based on the findings.

In order to do that, we will implement getFilesFromDir function in files.js:

/config/files.js - getFilesFromDir recursive function returns file paths from the given directory filtered out by file extensions

Then we can import and replace entry property with the modified result of getFilesFromDir function:

const path = require("path");

const getFilesFromDir = require("./config/files");

const PAGE_DIR = path.join("src", "pages", path.sep); const jsFiles = getFilesFromDir(PAGE_DIR, [".js"]);

const entry = jsFiles.reduce( (obj, filePath) => {

const entryChunkName = filePath.replace(path.extname(filePath), "").replace(PAGE_DIR, "");

obj[entryChunkName] = `./${filePath}`;

return obj;

}, {}); module.exports = {

entry: entry

};

Note: final version of webpack.config.js file can be found below (see gist file).

When the entry point is set, we can jump into the configuration of the html-webpack-plugin part. The main goal of this plugin is to generate an html file (in the dist folder), including corresponding javascript file. For instance, if we have generated a contact page, we expect to have contact.html with contact.js file included inside the <body> by <script> tags. At a minimum, we want to provide three configuration options:

plugins:[

new HtmlWebPackPlugin({

chunks:["contact", "vendor"],

template: "src/pages/contact.html",

filename: "contact.html"

})]

The chunks property defines codebase included into the template file via <script> tags and generates html file named filename as an output.

As we want to split vendor (i.e. 3rd party libraries) and app code into separate bundles, we have to add “vendor” element to the chunks table. I will write more about it when discussing the optimisation.

This plugin config is correct, but if we would like to follow this construction, we would have to hard-code creation of HtmlWebPackPlugin object for each html file. We don’t want to do that for the same reason as for the entry points definition. That’s why we will use getFileFromDir function again, but this time we will search only html files:

const HtmlWebPackPlugin = require("html-webpack-plugin");

const htmlFiles = getFilesFromDir(PAGE_DIR, [".html"]); const htmlPlugins = htmlFiles.map( filePath => {

const fileName = filePath.replace(PAGE_DIR, "");

return new HtmlWebPackPlugin({

chunks:[fileName.replace(path.extname(fileName), ""), "vendor"],

template: filePath,

filename: fileName})

}); module.exports = {

entry: entry,

plugins: [...htmlPlugins]

};

In order to import modules more easily we can also alias commonly used “components” and “src” folders by setting up alias for module resolver:

module.exports = {

entry: entry,

plugins: [...htmlPlugins],

resolve:{

alias:{

src: path.resolve(__dirname, "src"),

components: path.resolve(__dirname, "src", "components")

}

},

};

Now Webpack will use this alias when resolving module imports like the following:

import Menu from "components/Menu";

Instead of default import mechanism that is sensitive to changes in file location i.e.:

import Menu from "../../components/Menu";

Last but not least, we have to set babel-loader to make React work (i.e. transpile ES6 into ES5 code and JSX into javascript):

module.exports = {

// put previously defined properties here (entry, plugins etc)

module: {

rules: [{

test: /\.js$/,

exclude: /node_modules/,

use: {

loader:”babel-loader”,

options:{

presets: [

“@babel/preset-env”,

“@babel/preset-react”

]

}

}

}]

},

}

Note: alternatively presets can be put into .babelrc file

Finally, we can build project:

$ npm run build

And voilà! If everything went fine, we should see dist directory with html and js files generated on the base of “pages” folder.

However, there is still one problem with our js files. If you edit any of them, you will see that they include an app and third party source code (i.e. React with other libs).