nrwl / nx

Smart Monorepos · Fast CI
https://nx.dev
MIT License
23.39k stars 2.33k forks source link

Unused Exports Included in Bundle Despite Configuration #26380

Closed DavidN-Work closed 1 month ago

DavidN-Work commented 4 months ago

Current Behavior

I'm encountering an issue with tree shaking in an NX workspace where unused exports are still being included in the final bundle. Despite following best practices and ensuring the correct configurations, unused functions are not being properly excluded.

Expected Behavior

Only the functions actually used from JS libraries should be included in the final bundle, while unused functions should be excluded.

GitHub Repo

No response

Steps to Reproduce

  1. Run npx create-nx-workspace@latest tree-shaking-test --app --pm pnpm --interactive=false --preset=ts --workspaceType=integrated --nx-cloud=skip
  2. nx add @nx/express
  3. nx g @nx/express:app my-express-api --directory=apps/my-express-api
  4. nx g @nx/js:lib my-lib --directory=libs/my-lib
  5. Create three random functions in lib/my-lib/src/lib like so:
// getRandomGreeting.ts
export function getRandomGreeting(): string {
    const greetings = ["Hello", "Hi", "Hey", "Greetings", "Salutations"];
    const randomIndex = Math.floor(Math.random() * greetings.length);
    return greetings[randomIndex];
}
// getRandomAlphanumericString.ts
function getRandomAlphanumericString(length: number): string {
    const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
    let result = "";
    for (let i = 0; i < length; i++) {
        const randomIndex = Math.floor(Math.random() * characters.length);
        result += characters[randomIndex];
    }
    return result;
}
// getRandomSentence.ts
function getRandomSentence(wordCount: number): string {
    const words = ["quick", "brown", "fox", "jumps", "over", "lazy", "dog", "cat", "runs", "fast"];
    let sentence = "";
    for (let i = 0; i < wordCount; i++) {
        const randomIndex = Math.floor(Math.random() * words.length);
        sentence += words[randomIndex] + " ";
    }
    return sentence.trim() + ".";
}
  1. in the index.ts file at libs/my-lib/src export all three functions like so:
export * from './lib/getRandomSentence'
export * from './lib/getRandomAlphanumericString'
export * from './lib/getRandomGreeting'
  1. Use the function in the Express App like so:
import express from 'express';
import * as path from 'path';
import {getRandomGreeting} from '@tree-shaking-test/my-lib';

const app = express();

app.use('/assets', express.static(path.join(__dirname, 'assets')));

app.get('/api', (req, res) => {
  const demo = getRandomGreeting(5);
  res.send({ message: 'Welcome to my-express-api!', data: demo });
});

const port = process.env.PORT || 3333;
const server = app.listen(port, () => {
  console.log(`Listening at http://localhost:${port}/api`);
});
server.on('error', console.error);
  1. Edit the webpack.config.js and add usedExport: true and sideEffects: true :
const { NxAppWebpackPlugin } = require('@nx/webpack/app-plugin');
const { join } = require('path');

module.exports = {
  output: {
    path: join(__dirname, '../../dist/apps/my-express-api'),
  },
  plugins: [
    new NxAppWebpackPlugin({
      target: 'node',
      compiler: 'tsc',
      main: './src/main.ts',
      tsConfig: './tsconfig.app.json',
      assets: ['./src/assets'],
      optimization: false,
      outputHashing: 'none',
    }),
  ],
  optimization: {
    usedExports: true,
    sideEffects: true,
  },
};
  1. Add "sideEffects": false to the package.json of the my-lib project

  2. Update the tsconfig.base.json

    "module": "esnext",
    "target": "es6",
  3. Run nx run-many -t build

  4. Look at the main.js file in the dist and you'll see that it includes unused functions.

Nx Report

Node   : 20.9.0
OS     : darwin-arm64
npm    : 10.4.0

nx (global)        : 19.0.2
nx                 : 19.1.2
@nx/js             : 19.1.2
@nx/jest           : 19.1.2
@nx/linter         : 19.1.2
@nx/eslint         : 19.1.2
@nx/workspace      : 19.1.2
@nx/devkit         : 19.1.2
@nx/eslint-plugin  : 19.1.2
@nx/express        : 19.1.2
@nx/node           : 19.1.2
@nrwl/tao          : 19.1.2
@nx/web            : 19.1.2
@nx/webpack        : 19.1.2
typescript         : 5.4.5
---------------------------------------
Registered Plugins:
@nx/webpack/plugin
@nx/eslint/plugin
@nx/jest/plugin

Failure Logs

No response

Package Manager Version

pnpm 8.15.8

Operating System

Additional Information

This is my first time using NX so I'm not sure what the normal expected behaviour is but it looks like this issue appeared in #9717 and #3069. But was fixed in #17599.

The only way to fix the issue is with:

    "paths": {
      "@tree-shaking-test/my-lib": [
        "libs/my-lib/src/lib/getRandomAlphanumericString.ts",
        "libs/my-lib/src/lib/getRandomGreeting.ts",
        "libs/my-lib/src/lib/getRandomSentence.ts"
      ]
    }

Using the following:

    "paths": {
      "@tree-shaking-test/my-lib": [
        "libs/my-lib/src/lib/*",
      ]
    }

Yielded mixed results, with a success rate of about 50%. This might be due to my TS Server.

Coly010 commented 4 months ago

Thank you for bringing this issue to our attention! I want to assure you that I will take a look as soon as I can. Your patience and understanding in this matter is greatly appreciated.

Coly010 commented 1 month ago

Hey :)

Your setup is just ever so slightly incorrect. You only need to add

 "module": "esnext",
 "target": "es6",

to the tsconfig.app.json#compilerOptions as seen in this repo: https://github.com/Coly010/tree-shaking-test

github-actions[bot] commented 1 week ago

This issue has been closed for more than 30 days. If this issue is still occuring, please open a new issue with more recent context.