It should be self-evident that web applications keep growing in terms of their capabilities.
Web apps are close to or more powerful than their desktop counterparts. With this power, comes a lot of complexity. For a simple application, some of these complexities include: CSS and JavaScript minification, JavaScript and CSS code concatenation, image loading in JavaScript files, file watching, and auto-compilation. We will get into these in more detail later.
In light of this, several tools have been created to make development and shipping easier and more efficient. One such tool is webpack. There are many contenders in this field, Gulp and Browserify being two. In this tutorial, we will be demonstrating how to setup webpack for a React.js application. The reason we are using webpack is that many major web frameworks use it, including the official React.js compiler, create-react-app. Webpack is in fact the most popular build tool according to the 2018 State of JavaScript survey, as pictured below:
Please find the finished project code in this GitHub repo.
Compilation Requirements of a Simple Web Application
- Minification: This is the process of reducing code filesize. It is done by removing unnecessary whitespace. Other techniques include renaming functions and variable names.
- Concatenation: This is the method of combining several files into one.
- Image loading in JavaScript and CSS files: This is a method used to generate URLs for image files based on their configured location.
- File watching and auto-compilation: This is a method wherein a specified process will self-run when the contents of a file have changed.
- Auto-reloading: This goes hand in hand with file watching and auto-compilation. The only extra step it adds is that, after compilation, the page is auto-reloaded.
Summary of webpack Concepts
Webpack works with the concept of entry point and output. The entry and output setting are configured in a file called webpack.config.js
. Additional configurations are possible in this file and we will look at some of the common ones.
Entry Point
The entry point is a JavaScript file. It is the main file which will import all other required files. Using JavaScript import syntax, webpack knows how to read this entry file. It will also link up all the other files in there.
Output
This is a single JavaScript file. It will be the total of all the files which webpack has managed to process after reading the entry file. This is usually the script which we will end loading on our webpage using <script src="somepath/output.js"></script>
, for example. This process where we end up with a single file is called bundling. The resulting single file is usually called a bundle.
Modules
These are sets of rules which control how webpack will behave. An example would be: which file extensions to consider when concatenating JavaScript code.
Plugins
Plugins add extra capability to webpack to what already exists by default.
Setting up webpack for a Simple Web Application
We will begin with a simple React.js application.
Initialize an npm project using:
npm init -y
Install several npm packages below
npm install --save react react-dom prop-types // react stuff
npm install --save-dev webpack webpack-cli // webpack and it's cli
npm install --save-dev css-loader mini-css-extract-plugin // css compilation
npm install --save-dev babel-loader @babel/core @babel/preset-env @babel/preset-react // es6 and jsx stuff
npm install --save-dev html-webpack-plugin // inserts output script to index.html file
npm install --save-dev clean-webpack-plugin // to cleanup(or empty) the dist(or output) folder before compilation
npm install --save-dev sass-loader node-sass // sass to css compilation
npm install --save-dev file-loader // loading files, e.g. images, fonts
npm install --save-dev papaparse csv-loader xml-loader // xml, csv and tsvs loading
npm install --save-dev webpack-dev-server // webpack development server
In an empty folder, create a webpack config file with the name webpack.config.js
and insert the following content;
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
mode: "development",
entry: {
app: "./src/main.js"
},
devtool: 'inline-source-map',
devServer: {
contentBase: path.join(__dirname, './'), // where dev server will look for static files, not compiled
publicPath: '/', //relative path to output path where devserver will look for compiled files
},
output: {
filename: 'js/[name].bundle.js',
path: path.resolve(__dirname, 'dist'), // base path where to send compiled assets
publicPath: '/' // base path where referenced files will be look for
},
resolve: {
extensions: ['*', '.js', '.jsx'],
alias: {
'@': path.resolve(__dirname, 'src') // shortcut to reference src folder from anywhere
}
},
module: {
rules: [
{ // config for es6 jsx
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
},
{ // config for sass compilation
test: /\.scss$/,
use: [
{
loader: MiniCssExtractPlugin.loader
},
'css-loader',
{
loader: "sass-loader"
}
]
},
{ // config for images
test: /\.(png|svg|jpg|jpeg|gif)$/,
use: [
{
loader: 'file-loader',
options: {
outputPath: 'images',
}
}
],
},
{ // config for fonts
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: [
{
loader: 'file-loader',
options: {
outputPath: 'fonts',
}
}
],
}
]
},
plugins: [
new HtmlWebpackPlugin({ // plugin for inserting scripts into html
}),
new MiniCssExtractPlugin({ // plugin for controlling how compiled css will be outputted and named
filename: "css/[name].css",
chunkFilename: "css/[id].css"
})
]
};
Input JS File
Create an input JavaScript file in src/main.js
and paste in the following;
import React from "react";
import ReactDOM from "react-dom";
import Main from "@/components/Main";
import "./style.scss";
ReactDOM.render(<Main/>, document.getElementById('app'));
if (module.hot) { // enables hot module replacement if plugin is installed
module.hot.accept();
}
Create a React component file in src/components/Main.jsx
with the contents;
import React, { Component } from "react";
export class Main extends Component {
render() {
return (
<div>
<p className="hello-text">Hello from react!</p>
</div>
)
}
}
export default Main
Compiling React JSX to JavaScript (Presets)
Create a file at .babelrc
and put the following content;
{
"presets": ["@babel/preset-env", "@babel/preset-react"]
}
This sets which features of ES6 to load for React.js. Do not forget the period .
in the filename. It allows us to use the special syntax of React in native JavaScript code. Things like:
import Main from "@/components/Main";
<Main/>
Output a Single CSS File
Create a Sass file in src/style.scss
with the following contents;
.hello-text {
color: red;
}
Output a Single JavaScript File
In package.json
, add the following to the scripts section;
"dev": "webpack-dev-server"
"production": "webpack --mode production"
When we run the command, npm run dev
, the development server will be started. We can see the results of the running project at http://localhost:8080/
. Running npm run production
compiles the file in production mode and puts the result in the dist
directory.
Output Images
In the file src/components/Main.jsx
, import an image of your choice using the line:
import imagename from "@/images/imagename.jpg";
Make sure you store the image in the folder src/images/imagename.jpg
.
Use the image in the components render function using:
<p><img src={imagename} alt="Image name"/></p>
Now, the image should be visible in the browser.
Output Fonts
For fonts, inside the file src/style.scss
, load the fonts using a syntax similar to the following;
@font-face {
font-family: "Advent Pro";
font-style: normal;
font-weight: 400;
src: url("./fonts/advent-pro-v9-latin-regular.woff2") format("woff2"),
url("./fonts/advent-pro-v9-latin-regular.woff") format("woff");
}
In the case above, we are loading a font using two font files and giving it the name Advent Pro
Use the new font in the hello-text
class:
font-family: "Advent Pro";
Set Up File Watching
Due to the fact that we are using webpack-dev-server
, we automatically get file watching and auto-reloading.
Setting up webpack for More Advanced Web Applications
In addition to the above simple setup, let's add more features for a slightly more complex application.
Setting up Hot Module Replacement
This is similar to auto reloading except that it does not reload the page. Instead, it smartly injects only the parts of the files which have changed.
To add the functionality, add the following to the devServer
config in the webpack config file webpack.config.js
:
hot: true
Splitting Output JavaScript Files into Separate Files
Sometimes, we may want many output files for some reason. An example would be to reduce cache-busting impact because of files that change often. Create another file entry file in src/print.js
and add in the following:
console.log("This comes from print file");
This is just a simple log message in the console. In a real application though, we would probably have a lot more code in here.
Then, change the entry config like below;
entry: {
app: "./src/main.js",
print: "./src/print.js"
},
Now, we have two script files for the output.
Create Production Files
By now, you will notice that, when we run npm run dev
, there are no compiled files in the output folder dist
. That is because we are using the development server. If we want files for distribution, we need to use webpack's built-in compiler. We can do that by adding this to the script section of package.json
:
"build": "webpack",
Now, when we run npm run build
, a dist
folder will be created with the distribution files. To prepare that for production, add the flag as below:
"production": "webpack --mode production",
Clear Output Folders Before Regeneration
Sometimes, we may want to clear the dist
folder before creating the production files. One example is when you have file names randomly generated. In that case, there will be duplicates in some folders.
To do that, add the following to the list of plugins in the config file;
new CleanWebpackPlugin({
cleanOnceBeforeBuildPatterns: ["css/*.*", "js/*.*", "fonts/*.*", "images/*.*"]
}),
This is clearing all the folders named js
, fonts
and images
. To test out that it works, add a random JavaScript file to dist/js
. For example randomfile.js
.
Run npm run build
with the plugin configuration above commented out. You will notice that the file still remains.
Now uncomment the plugin configuration and rerun npm run build
. The file will now disappear.
Custom HTML Template
Create a file in src/index.html
with the following contents:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Learn Webpack</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
Now run npm run build
. Look at the output of the file in dist/index.html
. You will notice that it is not using the source HTML file in src/index.html
as a template because the titles are different. To configure that, change the HtmlWebpackPlugin
plugin in the webpack config file by passing in an object like below:
new HtmlWebpackPlugin({
template: "./src/index.html",
filename: "index.html",
title: "Learning Webpack"
}),
Now rerun npm run build
. You will notice that the titles are now the same.
Serving Other Static Asset Types
You will have noticed that when we build our project, the images and fonts are copied over to the dist
folder. We can not only copy over images and fonts but we can access in our code other file types like csv.
To add support for csv, create a file called src/mycsv.csv
and paste in some csv as so;
name,company,email,date
Raja,Sem Corporation,pede.ultrices.a@tinciduntpedeac.co.uk,"January 21st, 2019"
Aladdin,Ut Nulla Corp.,hendrerit.consectetuer@interdumSed.ca,"November 21st, 2018"
Plato,Fermentum Fermentum Limited,semper.egestas@massaMauris.net,"October 7th, 2019"
Anthony,Fringilla Est Consulting,luctus.ut@tortornibhsit.co.uk,"April 18th, 2018"
Then, add the following settings to the list of loader rules in the webpack config file:
{
test: /\.(csv|tsv)$/,
use: ["csv-loader"]
}
Now we can directly import the csv file in our code. In src/main.js
, add in these two lines of code:
Import the csv file first:
import CsvData from "./mycsv.csv";
Then, at the bottom of the file, add in console.log(CsvData);
Now, run npm run dev
. Open your browser and watch in your console. You should see the csv contents logged.
Protecting webpack Bundle Files
After building your app with webpack, if you open either one of the bundle files, you'll see that the whole logic of the code can easily be accessed. While this likely isn't a concern if you're building small projects, you should pay special attention if you're developing commercial web apps.
By reverse-engineering the application's source code, malicious actors may be able to abuse the app, tamper with the code, or even uncover important business logic (which is both a tendency and a concern in the Enterprise).
Webpack plugins like Uglify or webpack obfuscator only provide basic minification/obfuscation and can be quickly reversed with automated tools, and so fail to properly protect webpack bundle files. On the contrary, Jscrambler provides enterprise-grade JavaScript protection which cannot be reversed by automated tools and provides several layers of security, not just obfuscation.
To use the Jscrambler webpack plugin, first you have to install it:
npm i --save-dev jscrambler-webpack-plugin
Then, in the webpack.config.js
file, add this line:
const JscramblerWebpack = require('jscrambler-webpack-plugin');
And finally add the Jscrambler plugin to the plugin array in the same webpack.config.js
file:
plugins: [
new JscramblerWebpack({
enable: true, // optional, defaults to true
chunks: ['app', 'print'], // optional, defaults to all chunks
params: [],
applicationTypes: {}
// and other jscrambler configurations
})
]
During the webpack build process, the Jscrambler client will use the .jscramblerrc
config file. For more details, see the full integration tutorial.
Conclusion
By now, we have covered several aspects of webpack. It is a very dynamic script and asset management tool.
We have not used all of its features but these should be enough for your average application. For even more advanced tools, please refer to the official webpack documentation.
Originally published on the Jscrambler Blog by Lamin Sanneh.
Top comments (0)