Everything I know about writing modular JavaScript applications
TL;DR This article may eat up some time of yours. So please make sure about the time before sitting and following it step by step
When you start with a JavaScript project, you jump in creating the project layout, then includes those script files and stylesheets in the very lengthy HTML file. But soon when it becomes a medium sized project, you have the overhead of maintaining the project because your code starts bloating. How to apply a bandage to that bleeding code? You then starts looking at a framework and think of rewriting your application. You just think sitting there and code grow to a mammoth size(Traditionally front-end code has a bad reputation of doing simple jobs with more lines of code) increasing your development woes.
Finally, you feel bad when you add a new feature because the code is bulking up. You should search and apply the fix. It is a nightmare to scroll that thousand line javascript file where a variable assignment is breaking five other places. The global variables play a magical role in confusing you.
Old may not be gold in web development
Now companies are going with the cloud first approach. Do we still need to stick to the older practices and techniques while the web development world changed a lot? Have you ever thought how an e-commerce website like Flipkart, a cloud company is providing a butter like smooth user experience to the customers? We may not agree completely with statements like “tools are everything” but at the same time should keep an eye on modern development practices and workflows. Startups have the flexibility to chose the best from the beginning. They are even ready for the bigger technical challenges in the midst of their journey. It is not usual about the established enterprises seldom thinking out of the box. But with the changing world, hope they start it now. The traditional approaches are tiresome, more error prone and not viable in the future. At least for the new products, they should follow the path of startups.
Modularizing the web applications
Server applications come with the gift of modularization. Programming languages like Python, Java allows one to import variables and methods from other files. Even Node JS does that using module.exports. One can break things into multiple files keeping utility functions in one place and API handler functions in other.
The same thing is not supported by the browser. Browsers won’t allow one file to access another for security reasons. Instead, they ask you to load things in the memory as form of global variables and then use them.
For example in HTML
<script src="myjavascript1.js"></script>
<script src="myjavascript2.js"></script>
.........
<script src="myjavascript10.js"></script>
It is like including scripts one after the other so that the second script uses the variables and methods loaded by the first script. This is the plain old JavaScript development pattern. The sample plain old layout looks like this.
index.html
static/
js/
app/
vendor/
css/
app/
vendor/
All your custom logic goes into app/ directory. Plugins like Jquery, Datatables goes into vendor/ directory. The same case with CSS.
Why global variables and many script files are bad?
When your application grows and the number of files goes big, these global variables pollutes the namespace. Global variables also cause conflict with the variables from various libraries too. Since they are available throughout the program, they eat up a lot of space too. Browsers are the main culprit here.
The maintainability of the project becomes painful. It is very tough to debug the code at some point of time.
Coming to the multiple files, loading many files needs as many HTTP requests to be sent to the server and awaited for response. When you need to maintain the order of files, files should be loaded sequentially. For faster page speeds, you can bundle your CSS & JS
Benefits of Modularization
Modularization is the basic necessity for any software development. Breaking things into smaller pieces of functionality gives us the power to reuse the code. Modules are also containers for the namespaces.
- It increases the page load speed by on demand script loading
- It makes developers add features to a particular module without fuss
- It adopts the current specifications of ES6 and make JavaScript project viable in the future
Webpack 2: A great tool for modularizing your JS
Have you ever encountered the webpack tool? Maybe you did somewhere while checking out the Angular 2 tutorials. It is a dependency management tool that allows you write code in the modular fashion and bundles the static resources into a single file(even multiple if the file is big). Recently, I was working on a project which has the more scope for modularizing. Then, I stumbled upon Webpack 2.
If you tried Webpack before and turned your head away due to its complexity, then I am quite sure the below sections makes you feel comfortable with webpack
The main features of Webpack 2 are:
- Bundling the JS, CSS files and minify them
- Allowing modular programming through ES6 and babel-loader
- Tree shaking to reduce the bundle file sizes
ES6 introduced the idea of supporting modular programming inbuilt for JavaScript. babel helps us to compile the ES6 files to browser understandable ES2015 presets.
Webpack is a tool that helps you do your job faster. It does things which other tools are incapable of. It makes you write clean code and build them at the time of the code release.
It can also do an on-demand file loading to grab the static resources from the browser. It is the developer’s responsibility to find the right balance between bundling and loading of resources.
Let us see how by using Webpack2, we can improve the developer productivity, maintainability of code and, page load speeds.
Prerequisites
Source code for below steps is available at https://github.com/narenaryan/Webpack-Modular_Design
You should have these things installed before experimenting with Webpack
- Node JS
- NPM (Node Package Manager)
- Webpack
Please get the latest Node if you are beginning something because security patches and features are more in that.
Any project we build on top of the node is controlled through NPM. So first let us make a directory called project.
mkdir project
Now create an index.html and app, dist folders in the project directory.
app — consists of the source code for JS, CSS & Images
dist — consists of the bundled JS code
Initiate an npm package.json using this command
npm init
npm now asks you to enter details for the project name, maintainer etc. Press enter to fill default values. Create CSS and JS directories in the app folder.
mkdir app/css app/js
Now tree structure looks like this.
Add some boilerplate HTML code to the index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>My Modular APP</title>
</head>
<body>
<button class="myClass" type="button">Click Me!</button>
</body>
</html>
Now suppose we need to use jQuery in our project. The normal way is to include the JS file through an external CDN(Content Delivery Network). But wait a second. Don’t it adds overhead if we include many external JS files?Yes, it is. We need to bundle the jQuery with the application logic(custom JS)file. Then we only make a single request to the server from the client where this bundle is shipped.
For that to happen, install jQuery as a node package from the project directory.
npm install jquery --save
Remember this jQuery is not currently intended to use on Node. Instead, we need it for our front-end application
The above command creates a directory called node_modules in the project directory with jQuery package and its dependencies. — save flag is used to tell npm to update the dependencies section of package.json with the installed package. If you peek at the package.json it looks like
{
"name": "project",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"jquery": "^3.2.1"
}
}
Now install more packages using another npm command. Webpack is one of them. Don’t be overwhelmed by seeing the list of packages
npm install webpack uglifyjs-webpack-plugin babel-core babel-loader babel-preset-env underscore --save
I will tell why we need many packages here. webpack installs the webpack in our node_modules. babel-XXX packages are needed to convert the ES6 JS files to the older versions. uglifyjs-webpack-plugin is for minifying the bundle we finally create. underscore is a library which provides us many utility functions for processing arrays and maps in a functional programming style.
Now we are ready to go. Create a file called main.js in the project/app and add this code.
import $ from 'jquery';$(document).ready(function() {
$(".myButton").click(function () {
alert("Hello!");
});
});
This is a very simple example. It is just adding a click handler for the button in our HTML. What is the import syntax we used above? it is new. ES6 allows meaningful imports from other packages. jquery is the package name we installed before.
Now let us write a webpack configuration file. This file instructs webpack to create a bundle with an entry point and output points. The entry point is the source file/files and output is a bundled file/s. Since we are writing our JS code in ES6, we should specify webpack to use loaders like babel to convert them into normal JS before bundling.
vi webpack.config.js # From Project root
and add this content
const path = require('path');
const webpack = require('webpack');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');module.exports = {
entry: './app/js/main.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, './dist')
},
resolve: {
alias: {
$: "jquery/src/jquery",
}
},module: {
rules: [{
test: /\.js$/,
include: [path.resolve(__dirname, "./src/app")],
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['env']
}
}
}]
},
plugins: [
]
};
Many things are happening here. Ignoring the imports, let us see the configuration part with more importance.
- Entry is the JavaScript file where we have the main logic. Here it is app/js/main.js
- Output specifies the output file name and the directory where it should go to. Here file is bundle.js and directory to save is dist
- Resolve tells the webpack to replace all aliases with the functions from the given node package. In the above main.js, we are importing $ from “jquery”. We should add an alias to get $ function from jQuery.
- Module -> rules are there to specify what loaders webpack should use. Here we are mentioning babel to convert our ES6 code to JS and then bundle it.
- Plugins are intended to do the job after loaders finished their transpiling(converting the code). plugins can inject custom build steps. Above, we can use a plugin to uglify(minify & replace variables) our JavaScript bundle
Now we can order our webpack to create a bundle using this command from project root
./node_modules/webpack/bin/webpack.js
This command looks for webpack.min.js in our current directory and tries to bundle the code. The output looks like this if everything looks like this.
The bundle.js is a minified file which has code for both main.js and jQuery library.
If you try to run a simple command like this
webpack
It fails because the command is not in the path.
Now it is bad running a lengthy command with a relative path. So there are two alternatives. One is to add the current bin to PATH variable or use npm scripts section to add a build command. I prefer the second one.
Add these lines to the package.json to the scripts section.
“scripts”: {
“build”: “webpack”
}
After doing this we can build our bundles with command
npm run build
It outputs the same as the last example command. Here even though we are not specifying the executable externally, npm is clever enough to check the webpack first from the node_modules and then globally.
Now let us add a reference to this bundle.js in our index.html and launch it in a browser.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>My Modular APP</title>
<script src="dist/bundle.js" type="text/javascript"></script>
</head>
<body>
<button class="myButton" type="button">Click Me!</button>
</body>
</html>
We will see a button saying click me! click it and instantly you will see this alert message
What happened here! Webpack imported the JS code from jQuery and main.js then created a bundle.js. If you open and see the bundle.js, it has thousands of lines of JS code even though we wrote roughly 5 lines of JS. That is due to the concatenation of multiple JS files including library code and the dependency management logic of webpack
Now let us extend this example. Create another directory called math in app/js and add a file operations.js to it
mkdir app/js/math
touch app/js/math/operations.js
We are going to add few basic math operations in this file. Then we import those functions in main.js and use them. So add this code to the math/operations.js
function add(x, y) {
return x + y;
}function subtract(x, y) {
return x - y;
}function multiply(x, y) {
return x * y;
}function divide(x, y) {
return x / y;
}export default {
add: add,
subtract: subtract,
multiply: multiply,
divide: divide
}
The last statement is the ES6 way of exporting a map of variables and functions. Now modify the main.js to this
import $ from 'jquery';
import mathAPI from './math/operations.js';$(document).ready(function() {
$(".myButton").click(function () {
var message = "5 + 3 = " + mathAPI.add(5,3) + " 5 * 3 = "+ mathAPI.multiply(5,3)
alert(message);
});
});
and rebuild the bundle using command
npm run build
Now refresh the browser and click the button, you will see
How this is different to the traditional script loading. Here webpack is taking care of all the dependency management and allowing us to write clean modular code. If you see the directory structure now
Which is pretty looking and neat
The benefits we get by using webpack are:
- Bundling (reducing the latency of multiple resources)
- Ordering & Overcoming evil global variables
- Modularization of code
- Developing front-end on Node
- We can integrate tests easily in our project with NPM
You can also export and import ES6 consts, functions and, objects in various ways. For more details see this wonderful stack overflow thread.
https://stackoverflow.com/questions/25494365/es6-javascript-module-export-options
Tree shaking
Tree shaking is the feature of including the necessary functions from libraries or other source files and removing the code that is not used. Our code might be using few imports from the other files. Then there is no need to include source code for unused functions in the webpack bundles. Webpack 2 inspiring from a library called Rollup can do tree shaking with help of Babel. Babel import/export can be easily exploited to implement tree shaking feature.
For example, Underscore.JS provides over 100+ utility functions for different use cases. The most commonly used ones are map, filter, times, union etc. Is it reasonable for webpack to bundle the entire Underscore JS for the sake of these few functions into our bundle.js. It should not.
Let us add one more function for filtering the even numbers from a given list. Even though we can easily implement it in JS, I here use Underscore’s filter method to do it in the functional style.
Modify the math/operations.js to this
import {filter} from "underscore";function add(x, y) {
return x + y;
}function subtract(x, y) {
return x - y;
}function multiply(x, y) {
return x * y;
}function divide(x, y) {
return x / y;
}function evenNums(list){
return filter(list, function(num) {return num % 2 == 0})
}export default {
add: add,
subtract: subtract,
multiply: multiply,
divide: divide,
evenNums: evenNums
}
and main.js to this
import $ from 'jquery';
import mathAPI from './math/operations.js';$(document).ready(function() {
$(".myButton").click(function () {
var message = "5 + 3 = "
+ mathAPI.add(5,3)
+ ", 5 * 3 = "
+ mathAPI.multiply(5,3)
+ ", Even in [1,2,3,4,5] are "
+ mathAPI.evenNums([1,2,3,4,5])
alert(message);
});
});
The output will be
See our underscore is worked. Webpack bundled it along with our main.js and operations.js code but if you see the bundle it only includes the source code for filter and it’s depended on functions but not the whole library. This is the benefit of tree shaking
Tree shaking reduces the size of bundles drastically
Don’t you think that webpack with help of loaders is allowing us to write clean code with bundling resources? The above steps can be used to bundle CSS and image resources too.
The source code in this article is available at this Github repo.
Hoping that you along with me journeyed through the article to know about modularizing a JS app. Expecting your thoughts about this! Reply me at
https://twitter.com/@Narenarya3
Thanks for reading :)