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.
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
.
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.
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.
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.
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.
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.
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!
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!
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
Handle frontend data discrepancies with eventual consistency using WebSockets, Docker Compose, and practical code examples.
Efficient initializing is crucial to smooth-running websites. One way to optimize that process is through lazy initialization in Rust 1.80.
2 Replies to "Tree shaking JSON files with webpack"
Hey thanks for the article. In the last part about CSS free shaking, shouldn’t it be `sideEffects: false` instead?
Cheers,
Salim
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!