Minification and obfuscation of JavaScript applications

What is JavaScript Code Obfuscation?

JavaScript code obfuscation is a technique employed to transform code into a less readable and more challenging-to-understand form, making it difficult for unauthorized individuals to reverse engineer or tamper with your code.

At its core, obfuscation is the process of transforming code into a form that is difficult to understand for humans, yet still functional for machines. In the context of JavaScript, this means disguising the source code without altering its functionality. The goal is to make it challenging for potential adversaries to reverse-engineer or comprehend the original code’s logic.

Keep in mind that obfuscation doesn’t provide bulletproof security or encryption. Instead, it serves as a deterrent, raising the bar for attackers and making their efforts less worthwhile. Determined hackers can still reverse-engineer obfuscated code, but obfuscation can slow them down and make it less economically viable.

Reasons to Obfuscate JavaScript Code

1. Intellectual Property Protection

One of the primary motivations for obfuscating JavaScript code is to safeguard intellectual property. Companies invest significant time, effort, and resources into developing innovative algorithms and logic. Obfuscation makes it more difficult for competitors to copy or understand these valuable assets.

2. Preventing Code Tampering

Obfuscation can also aid in preventing malicious tampering with your JavaScript code. By obfuscating the code, attackers won’t easily identify vulnerable points for injecting harmful scripts or exploits.

3. License Enforcement

For commercial software, developers may use obfuscation to enforce licensing terms. By making it harder to modify or remove license checks, the developer can better control access to their software.

4. To prevent code theft

 Obfuscation can make it more difficult for people to steal your code. If someone does manage to steal your code, they will have a harder time understanding it, which can make it more difficult for them to use it.

5. Reduced Bandwidth Usage

Minification, which is often part of the obfuscation process, reduces the code’s size, leading to decreased bandwidth usage and faster load times for your web application.

Techniques for Obfuscating JavaScript Code

1. Minification

Minification is the first step in the obfuscation process. It involves removing whitespace, shortening variable and function names, and stripping comments from the code. Although this technique doesn’t truly obfuscate the code, it makes it less readable for humans. Several tools, such as UglifyJS and Terser, can perform JavaScript minification.

2. Renaming Variables and Functions

Meaningful variable and function names are a developer’s best friend, but they can also be a goldmine for reverse engineers. By renaming variables and functions to cryptic and non-descriptive names, you increase the complexity for anyone trying to understand your code.

3. String and Function Encryption

Consider encrypting strings and functions within the code. You can decrypt them at runtime, but it adds an extra layer of difficulty for potential attackers.

4. Code Splitting and Self-Executing Functions

Break your code into multiple files and wrap it within self-executing functions. This technique not only enhances performance but also makes it harder for attackers to grasp the entire codebase.

5. Control Flow Obfuscation

This method involves transforming the control flow of your code. It includes techniques like code flattening, loop unrolling, and random code insertion, making it challenging to understand the actual execution sequence.

6. Dead Code Injection

Inject dead code that does not affect the functionality of your application but distracts reverse engineers.

Minification & Obfuscating of JS application using Webpack

Obfuscating an application based on ES modules involves applying JavaScript obfuscation to the entire codebase, including modules and any related dependencies. To accomplish this, we’ll leverage the widely-used “terser” library, which is a popular choice for code minification and obfuscation in JavaScript applications. Additionally, we’ll utilize a build tool like rollup to handle both the building and obfuscation process.

If you’re working with ES modules and wish to obfuscate your code, you can proceed by following these steps:

Step 1: Initialize npm at  root of your client-side folder:

npm init -y 

Step 2: Install required packages in the root of your folder:

npm i -D webpack webpack-cli terser-webpack-plugin

Step 3: Create the webpack configuration file named webpack.config.js in the root of your project with the following content:

const path = require('path');
const TerserPlugin = require('terser-webpack-plugin');


module.exports = {
   mode: 'production',// enables various webpack optimisations
   entry: {
     app: './src/index.js', // project main entry file
     // add other js asset files
   },
   output: {
       path: path.resolve(__dirname, 'dist'), // output directory
       publicPath: '/dist/', //  Public URL path
       filename: "[name].js", // [name] will be replaced by the entry name
       chunkFilename: "[name][contenthash].js", // use [name].[contenthash].js to include hash
       clean: true, // clean the output directory before each build
   },
   optimization: {
     splitChunks: {
       chunks: 'all',
     },
     minimize: true,
     minimizer: [new TerserPlugin({
       parallel: true,
       terserOptions: {
         mangle: true,  //Enable variable name mangling
         compress: {
           drop_console: true, // Remove console.log statements
           drop_debugger: true,
         },
         output: {
           comments: false, // Remove comments
         },
       },
     })],
   },
};

Example project main entry file : src/index.js

import App from './app.vue.js'
import router from './routes/index.js'

const { createApp } = Vue;

const app = createApp(App)
app.use(router)
app.mount('#app');

Step 4: Add a script { “build”: “webpack –config webpack.config.js” }, in your package.json file

 {
  …
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
   "build": "webpack --config webpack.config.js",
    …
 },

  …
 }

Step 5: Run the following command to create obfuscated & minified files of your application:

npm run build

After executing these steps, your application will be bundled and will be available in the dist folder

Step 6: Point the main entry file in your ‘index.html’ from ‘dist’ folder & test your application

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <title>APP</title>
</head>
<body>
  <main id="app" class="wrapper"></main>
  <!-- change src path as per config -->
 <script type="module" src="./dist/app.js"></script>
 <!-- vue & vue-router -->
 <script src="assets/js/vue.global.min.js"></script>
 <script src="assets/js/vue-router.global.min.js"></script>
</body>
</html>

Step 7: Webpack provides various plugins like: PurgeCSSPlugin and MiniCssExtractPlugin that scans your codebase and removes unused CSS to reduce the file size of your stylesheets

It only works if you’re importing css in your js files using import statement:

import('../store-client/assets/css/bootstrap.min.css')

Install following packages:

npm i -D purgecss-webpack-plugin mini-css-extract-plugin css-loader
const path = require('path');
const TerserPlugin = require('terser-webpack-plugin');
const {PurgeCSSPlugin} = require('purgecss-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const glob = require('glob')


const PATHS = {
 src: path.join(__dirname, 'src')
}


module.exports = {
   mode: 'production',
   entry: {
     app: './src/index.js', // project main file
   },
   output: {
       path: path.resolve(__dirname, 'dist'),
       publicPath: '/dist/',
       filename: "[name].[contenthash].js",
       chunkFilename: "[name].[contenthash].js",
       clean: true,
   },
   optimization: { … },
   module: {
     rules: [
       {
         test: /\.css$/,
         use: [
           MiniCssExtractPlugin.loader,
           "css-loader"
         ]
       }
     ]
   },
   plugins: [
     new MiniCssExtractPlugin({
       filename: '[name].[contenthash].css',
     }),
     new PurgeCSSPlugin({
       paths: glob.sync(`${PATHS.src}/**/*`,  { nodir: true }),
     }),
   ],
};

Optimizing CSS Files with PurgeCSS

To optimize the external/ custom css that are linked in html we can use following packages cssnano, postcss and purgecss

Step 1:  Initialize npm & install following packages

npm init -y

npm i -D postcss purgecss cssnano

Step 2: Create the purgecss configuration file named purgecss.config.js in the root of your project with the following content:

module.exports = {
 content: [
   './store-client/src/**/*.js',
   './store-client/assets/js/*.js'
 ], // add HTML, JS, Vue, and JSX files to scan
 css: [
   './store-client/assets/css/bootstrap.min.css',
   './store-client/assets/css/style.css',
   './store-client/assets/css/font-awesome.css',
   './store-client/assets/css/line-icon.css',
   // files to optimize
 ],
 safelist: ['flex-center', 'banner-text-box' ],
 output: './store-client/assets/dist' // output folder
};

Step 3: Create script file named purgecss.js in the root of your project with the following content:

const { PurgeCSS } = require('purgecss');
const postcss = require('postcss');
const cssnano = require('cssnano');
const fs = require('fs');
const path = require('path');


const config = require('./purgecss.config.js');
const outputPath = config.output;


async function runPurgeAndMinifyCSS() {
 try {
   await fs.promises.mkdir(outputPath, { recursive: true });


   const purgeCSS = new PurgeCSS();
   const purgeCSSResults = await purgeCSS.purge(config);


   // process optimized CSS files
   for (const purgeCSSResult of purgeCSSResults) {
     const filename = path.basename(purgeCSSResult.file);
     const filepath = path.join(outputPath,filename);


     // minify optimized CSS file
     const minifiedCSS = await postcss([cssnano()])
         .process(purgeCSSResult.css, { from: undefined });


     fs.writeFileSync(filepath, minifiedCSS.css, 'utf8');
     console.log(`minified file CSS saved to ${filepath}`);
   }


 } catch (error) {
   console.error('Error running PurgeCSS and minifying CSS:', error);
 }
}


async function initPurgeAndMinifyCSS() {
 await runPurgeAndMinifyCSS()
 console.log('PurgeAndMinifyCSS complete.');
}


initPurgeAndMinifyCSS();

Step 4: Run the script directly using `node purgecss.js`

node purgecss.js

 or a purgecss script in package.json file & run `npm run purgecss`


"scripts": {
   "purgecss": "node purgecss.js"
 },
npm run purgecss