CC 4.0 License

The content of this section is derived from the content of the following links and is subject to the CC BY 4.0 license.

The following contents can be assumed to be the result of modifications and deletions based on the original contents if not specifically stated.

Tree shaking

Rspack supports tree shaking, which is a term commonly used within a JavaScript context to describe the removal of dead code.

It relies on the import and export statements to detect if code modules are exported and imported for use between JavaScript files.

Tree shaking will be enable when you set mode into production.

Basic tree shaking

rspack.config.js
/**
 * @type {import('@rspack/core').Configuration}
 */
const config = {
  mode: 'development',
  entry: {
    index: './src/index.js',
  },
  output: {},
};

module.exports = config;
./src/index.js
import { cube } from './math.js';

function component() {
  const element = document.createElement('pre');

  element.innerHTML = ['Hello webpack!', '5 cubed is equal to ' + cube(5)].join(
    '\n\n',
  );

  return element;
}

document.body.appendChild(component());
./src/math.js
export function square(x) {
  return x * x;
}

export function cube(x) {
  return x * x * x;
}

Note that we did not import the square method from the src/math.js module. That function is what's known as "dead code", meaning an unused export that should be dropped. Now let's build the project. This will yield the following result:

// ...skipping some trivial code
var __webpack_modules__ = {
  './src/index.js': function (module, exports, __webpack_require__) {
    'use strict';
    Object.defineProperty(exports, '__esModule', {
      value: true,
    });
    var _mathJs = __webpack_require__('./src/math.js');
    function component() {
      const element = document.createElement('pre');
      element.innerHTML = [
        'Hello webpack!',
        '5 cubed is equal to ' + (0, _mathJs.cube)(5),
      ].join('\n\n');
      return element;
    }
    document.body.appendChild(component());
  },
  './src/math.js': function (module, exports, __webpack_require__) {
    'use strict';
    Object.defineProperty(exports, '__esModule', {
      value: true,
    });
    function _export(target, all) {
      for (var name in all)
        Object.defineProperty(target, name, {
          enumerable: true,
          get: all[name],
        });
    }
    _export(exports, {
      square: function () {
        return square;
      },
      cube: function () {
        return cube;
      },
    });
    function square(x) {
      return x * x;
    }
    function cube(x) {
      return x * x * x;
    }
  },
};
// ...

As you can see, if we don't enable the tree shaking, all the code is kept as is, except wrapping with some runtime code.

Now, we switch to production mode and rebuild the project. In order to make the output more readable, we also turn off the minimize option and switch the moduleIds to named. To compare with the later chapters, we turn off optimization.sideEffects.

rspack.config.js
/**
 * @type {import('@rspack/core').Configuration}
 */
const config = {
  mode: 'production',
  entry: {
    index: './src/index.js',
  },
+ optimization: {
+   sideEffects: false,
+   moduleIds: 'named',
+   minimize: false
+ }
};

module.exports = config;

Rebuild the project, the square function got dropped.

SideEffects

In a 100% ESM module world, identifying side effects is straightforward. However, we aren't there quite yet (In real project we using different kinds of format of package cjs, esm, umd and so on.), so in the mean time, it's necessary to provide hints to rspack's compiler on the "pureness" of your code. The way this is accomplished is the "sideEffects" package.json property.

package.json
{
  "name": "your-project",
  "sideEffects": false
}

The sideEffects field supports the following values:

  • false All files in this package have no side effects.
  • string A glob matching files that includes side effects.
  • Array<string> An array of globs matching files that include side effects.
  • undefined This is the default value of sideEffects when you don't provide any value to sideEffects field of package.json, When the undefined is set, Rspack will try to analyse if the code has sideEffects when optimization.sideEffects is true (which is the default value when mode is production), or Rspack will treat all the modules in package has sideEffect

This time, we use a more complicated demo.

rspack.config.js
/**
 * @type {import('@rspack/core').Configuration}
 */
const config = {
  mode: 'production',
  entry: {
    index: './src/index.js',
  },
  optimization: {
    moduleIds: 'named',
    minimize: false,
  },
};
module.exports = config;
index.js
import { multiply } from 'math';

console.log(multiply(2, 3));
node_modules/math/package.json
{
  "name": "math",
  "sideEffects": false
}
node_modules/math/index.js
export * from './add.js';
export * from './multiply.js';
export * from './subtract.js';
node_modules/math/subtract.js
export const subtract = (a, b) => a - b;
node_modules/math/multiply.js
export const multiply = (a, b) => a * b;
node_modules/math/add.js
const randomDate = Date.now();
export const addRandomDate = a => a + randomDate;
export const add = (a, b) => a + b;

The variable randomDate would still be needed because it includes a side effect that runs during the module's initialization. However, because the package.json includes the sideEffects field and the value is false, and there are no export variable used in add.js, so the whole module could be skipped, same for subtract.js

//...
var __webpack_modules__ = {
  './node_modules/math/index.js': function (
    module,
    exports,
    __webpack_require__,
  ) {
    'use strict';
    Object.defineProperty(exports, '__esModule', {
      value: true,
    });
    __webpack_require__.es(
      __webpack_require__('./node_modules/math/multiply.js'),
      exports,
    );
  },
  './node_modules/math/multiply.js': function (
    module,
    exports,
    __webpack_require__,
  ) {
    'use strict';
    Object.defineProperty(exports, '__esModule', {
      value: true,
    });
    Object.defineProperty(exports, 'multiply', {
      enumerable: true,
      get: function () {
        return multiply;
      },
    });
    const multiply = (a, b) => a * b;
  },
  './src/index.js': function (module, exports, __webpack_require__) {
    'use strict';
    Object.defineProperty(exports, '__esModule', {
      value: true,
    });
    var _indexJs = __webpack_require__('./node_modules/math/index.js');
    console.log((0, _indexJs.multiply)(2, 3));
  },
};
// ...

module.rule.sideEffects

You could override some modules sideEffects by using module.rule.sideEffects. Why do we need such feature? Considering if the author of package math forgot to add sideEffects field in package.json:

node_modules/math/package.json
{
+ "name": "math"
- "name": "math",
- "sideEffects": false
}

Then Rspack will try to analyze the code safely and only mark the module is sideEffectFree when all toplevel statements are sideEffectFree. As you can see, math/index.js, math/subtract.js and math/multiply.js is sideEffectFree while math/add.js is not, because of const randomDate = Date.now(). When rebuilding the project, you could see the diff:

//...
var __webpack_modules__ = {
"./node_modules/math/index.js": function (module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
__webpack_require__.es(__webpack_require__("./node_modules/math/multiply.js"), exports);
+__webpack_require__.es(__webpack_require__("./node_modules/math/add.js"), exports);
},
"./node_modules/math/multiply.js": function (module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
Object.defineProperty(exports, "multiply", {
    enumerable: true,
    get: function() {
        return multiply;
    }
});
const multiply = (a, b)=>a * b;
},
+"./node_modules/math/add.js": function (module, exports, __webpack_require__) {
+ "use strict";
+ Date.now()
+},
"./src/index.js": function (module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
var _indexJs = __webpack_require__("./node_modules/math/index.js");
console.log((0, _indexJs.multiply)(2, 3));
},

}
// ...

That's not what we expect, then we could use module.rule.sideEffects, because it has higher priority than sideEffects in package.json. Since the initialization side effect is only meaningful when addRandomDate is utilized, we can safely override it. To do so, we can make the following modification to our rspack.config.js:

rspack.config.js
/**
 * @type {import('@rspack/core').Configuration}
 */
const config = {
  mode: 'production',
  entry: {
    index: './src/index.js',
  },
  optimization: {
    minimize: false,
    moduleIds: 'named'
  },
+  module: {
+    rules: [
+      {
+        test: /math\/add\.js/,
+        sideEffects: false
+      }
+    ]
+  }
};
module.exports = config;

Rebuilding the project, we could got the same result as before, the whole math/add.js module got dropped.

Reexports optimization

After enabling SideEffects optimization, Rspack tries to optimize re-exports modules.

sdk.js
export { a } from './a.js';
export { b } from './b.js';
export { c } from './c.js';
// ...
index.js
import { a } from './sdk.js';
console.log(a);

If Rspack knows that module sdk.js has no side effects, Rspack will try to import a directly from a.js rather than imports the whole sdk.js.

In some non-standard libraries, there may be circular dependencies, for example, a.js imports members from b.js via sdk.js.

a.js
import { b } from './sdk.js';
export const a = b;
index.js
import { a } from './sdk.js';
console.log(a);

At this time, there is a circular dependency between a.js and sdk.js, which is not recommended to intentionally use circular dependencies like this. However, it can still be built successfully and run successfully. This is because Rspack will attempt to transform the above imports into:

a.js
- import { b } from './sdk.js';
+ import { b } from './b.js';
export const a = b;
index.js
- import { a } from './sdk.js'
+ import { a } from './a.js'
console.log(a);

After the transformation, the circular dependency is gone. However, if you try to turn off optimization.providedExports or optimization.sideEffects, the build will be successful, but errors due to circular dependencies will occur at runtime.

You can find above example here.

This diagram demonstrates the process of re-exports optimization: