Kumar Abhirup I am Kumar Abhirup, founder at Beam Community, Inc. I'm in school and from Pune, India. I write about coding, tech, and everything I know.

Tree shaking JSON files with webpack

4 min read 1206

Treeshaking JSON in Webpack

Tree shaking is critical to developers because it removes dead code and unused functions, variables, and methods that take up space in your final production build.

Popular tools like rollup.js and webpack perform out-of-the-box tree shaking when compiling multiple JavaScript files into a single one.

In frontend development, modules and packages are managed in various ways to achieve the best results. During development, your code will be split into many small modules.

When you deploy your application, you should bundle these modules into one — or a few very large — files, preferably using webpack.

Why, you ask? Here are a few reasons.

  • A bundled file is easier to compress versus multiple, non-bundled JavaScript files
  • When loading all the modules, it’s preferable to load them from fewer files rather than several
  • Unused exports get removed when bundling (i.e., tree shaking), helping to save space and reduce the final build size

Tree shaking JSON files

Imagine you have this JSON file, named strings.json.

{
  "usedString1": "Hello world!",
  "usedString2": "Hello world again!",
  "unusedString": "I'm never used in the code ever"
}

Now, try to access its content in JavaScript index.js, like so:

const strings = require("./strings.json");
console.log(strings.usedString1, strings.usedString2);

You’d see we are only using one key in the entire JSON file, the usedString. unusedString is dead code, so we’re not using it. Yet, when you import/require strings.json, the dead code comes with it and makes its way into your final build, taking significant space and increasing your file size unnecessarily.

Currently, when you bundle with webpack, dead JSON code is not removed by default. You must use a webpack plugin, generally webpack-json-access-optimizer.

Let’s see how this plugin tree shakes your code using a JSON file.

The plugin first converts the JSON structure into an array, so strings.json now looks like this:

["Hello world!", "Hello world again!"]

The plugin compiles JavaScript code to adjust to this new array structure, so index.js now looks like this:

const strings = require("./strings.json");
console.log(strings[0], strings[1]);

Notice in strings.json that the plugin removes the unused value, unusedString.

Using webpack-json-access-optimizer

Here’s a GitHub demo I’ve created. First, clone it.

git clone https://github.com/KumarAbhirup/webpack-treeshaking-json

Now, run yarn, yarn build, and yarn start.

If you check webpack.config.js, you’ll see it’s pretty simple, except from lines 16 to 33.

const path = require('path');
const DIST_DIR = path.resolve(__dirname, 'dist');

const { JsonAccessOptimizer } = require('webpack-json-access-optimizer');
const { ProvidePlugin } = require('webpack');

let config = {
    entry: './src/index.js',
    output: {
        filename: 'bundle.js',
        path: DIST_DIR
    },
    resolve: {
        extensions: ['.js', '.json']
    },
    module : {
        rules: [
            {
                test: /strings\.json$/,
                use: [
                    'webpack-json-access-optimizer', 
                ],
                type: 'json'
            }
        ]
    },
    plugins: [
        new ProvidePlugin({
            $t: './$tProvider'
        }),
        new JsonAccessOptimizer({
            accessorFunctionName: '$t', // i18n function name
        })
    ],
    optimization: {
        usedExports: false,
    },
    devtool: 'source-map'
};
module.exports = config;

Take a closer look here:

module : {
    rules: [
        {
            test: /strings\.json$/,
            use: ['webpack-json-access-optimizer'],
            type: 'json'
        }
    ]
},
plugins: [
    new ProvidePlugin({
        $t: './$tProvider'
    }),
    new JsonAccessOptimizer({
        accessorFunctionName: '$t', // i18n function name
    })
],

Notice we tell webpack to parse strings.json using the webpack-json-access-optimizer plugin so we can tree shake the JSON file.



In the plugins section, we make the $t function available globally so that all files will be able to access strings.json, like so: $t('usedString1').

Now, check out ./src/$tProvider.js.

const t = require("./strings.json");
const $t = keyString => {
  return t?.[keyString];
};
module.exports = $t;

It fetches all key-value pairs from strings.json, then exports a default function that returns the value by taking in a key string.

Let’s look at our ./src/index.js file. We are using the $t function that we made available on a global scope.

console.log($t("usedString1"), $t("usedString2"));

Now, if you code yarn build && yarn start, you should see an output like this.

➜  webpack-treeshaking-json git:(main) yarn start
yarn run v1.19.1
$ node dist/bundle.js
Hello world! Hello world again!
✨  Done in 0.26s.

Reviewing the compiled code

Let’s check out the compiled code in ./dist/bundle.js:

(()=>{var r,o={500:(r,o,e)=>{const t=e(46);r.exports=r=>t?.\[r]},46:r=>{"use strict";r.exports=JSON.parse('["Hello world!","Hello world again!"]')}},e={};r=function r(t){var s=e[t];if(void 0!==s)return s.exports;var l=e[t]={exports:{}};return o[t\](l,l.exports,r),l.exports}(500),console.log(r(0),r(1))})();
//# sourceMappingURL=bundle.js.map

You’ll see it only uses key-value pairs from strings.json that were actually used in the code, and it omits the unusedString. This way, you save important space in your final build.

Tree shaking and removing unused exports

Let’s look at this JavaScript file.

const sum = (a, b) => {
  return a + b;
};

const multiply = (a, b) => {
  return a * b;
};

const divide = (a, b) => {
  return a / b;
};

console.log(sum(1, 9));

module.exports = { sum, multiply };

You can see that the divide function is present in the code but is not being put to use anywhere, but functions such as sum and multiply are being used in the code.


More great articles from LogRocket:


sum() was used in the console.log() statement, and also in the module.exports when we exported that function. multiply() is used in module.exports upon exporting the function.

If you compile this without tree shaking, the divide function will be present in the compiled code, hogging space even though it isn’t being used.

Additional learning

Eliminating unused exports using webpack

Eliminating unused exports with webpack also helps save space, greatly reduce the final build size, and result in quicker page load times.

Use this setting for your webpack config:

optimization: {
    usedExports: true
}

webpack.config.js should now look like this:

const path = require('path');
const DIST_DIR = path.resolve(__dirname, 'dist');

const { JsonAccessOptimizer } = require('webpack-json-access-optimizer');
const { ProvidePlugin } = require('webpack');

let config = {
    entry: './src/index.js',
    output: {
        filename: 'bundle.js',
        path: DIST_DIR
    },
    resolve: {
        extensions: ['.js', '.json']
    },
    module : {
        rules: [
            {
                test: /strings\.json$/,
                use: [
                    'webpack-json-access-optimizer', 
                ],
                type: 'json'
            }
        ]
    },
    plugins: [
        new ProvidePlugin({
            $t: './$tProvider'
        }),
        new JsonAccessOptimizer({
            accessorFunctionName: '$t', // i18n function name
        })
    ],
    optimization: {
        usedExports: true,
    },
    devtool: 'source-map'
};

module.exports = config;

Notice the usedExports: true above. With it, unused functions and variables won’t make it in your final build and compiled code.

Tree shaking CSS

If you have configured webpack to import CSS modules in JS before, you might want to learn how to tree shake the CSS you are importing. CSS code often goes unused, so this would be helpful to optimize your app.

Say you have a CSS rule in your webpack config:

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

You just have to add the sideEffects: true property to it.

{
  test: /\.css$/,
  use: ['style-loader', 'css-loader'],
  sideEffects: true
}

After doing so, and assuming your webpack config mode: 'production' is set, it should tree shake all of your imported CSS files during compilation — awarding you a huge bundle size reduction to make apps production-ready!

Conclusion

Tree shaking your code is critical when you compile code. Webpack does perform tree shaking for JavaScript exports, but it doesn’t do so with JSON files unless you use webpack-json-access-optimizer. Using these techniques in your projects should save you space and optimize your apps. Happy coding!

: Full visibility into your web and mobile apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.

.
Kumar Abhirup I am Kumar Abhirup, founder at Beam Community, Inc. I'm in school and from Pune, India. I write about coding, tech, and everything I know.

2 Replies to “Tree shaking JSON files with webpack”

  1. Hey thanks for the article. In the last part about CSS free shaking, shouldn’t it be `sideEffects: false` instead?

    Cheers,
    Salim

    1. Hi, Kumar here. I checked the article & scraped the internet about this, I can confirm it should be `sideEffects: true`.

      Though, would love to know why you think it should be `false` instead. Thank you so much for reading, love the feedback!

Leave a Reply