Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Client packages can't be tree-shaken properly #6889

Open
4 tasks done
spaceemotion opened this issue Feb 16, 2025 · 5 comments
Open
4 tasks done

Client packages can't be tree-shaken properly #6889

spaceemotion opened this issue Feb 16, 2025 · 5 comments
Assignees
Labels
bug This issue is a bug. p2 This is a standard priority issue potential-regression Marking this issue as a potential regression to be checked by team member

Comments

@spaceemotion
Copy link

spaceemotion commented Feb 16, 2025

Checkboxes for prior research

Describe the bug

I noticed that our lambda bundle contains pretty much all S3 and DynamoDB commands, even though I recently refactored to use the smaller, non-aggregated clients instead.

Image

Image

Regression Issue

  • Select this option if this issue appears to be a regression.

SDK version number

@aws-sdk/client-s3 3.749.0, @aws-sdk/client-dynamodb 3.749.0

Which JavaScript Runtime is this issue in?

Node.js

Details of the browser/Node.js/ReactNative version

v22.2.0

Reproduction Steps

Our setup: esbuild, using the following config:

{
  bundle: true,
  treeShaking: true,
  minify: true,
  sourcemap: true,

  legalComments: "none",

  platform: "node",
  target: "es2020",
  mainFields: ['module', 'main'],

  conditions: [
    'wintercg',
    'import',
    'module',
    'node',
  ],

  define: {
    'process.env.NODE_ENV': '"production"',
  },

  loader: {
    ".node": "file",
  },

  alias: {
    'lodash': 'lodash-es',
  },
}

Observed Behavior

From what I can tell, the tree shaking stops working because the "S3.js" and "DynamoDB.js" files include a list of all commands to support on the clients. Even though we're not importing those files/clients (and use the raw S3Client and DynamoDBClient instead), the file likely still gets evaluated:

createAggregatedClient(commands, DynamoDB);

createAggregatedClient(commands, S3);

There already has been an issue up on StackOverflow about this:

https://stackoverflow.com/q/79169593/1397894

Here is an analysis by esbuild:

Imported file src/services/dynamoDbClient.ts contains:
  import "@aws-sdk/client-dynamodb";

Imported file ../../node_modules/.pnpm/@aws-sdk+client-dynamodb@3.749.0/node_modules/@aws-sdk/client-dynamodb/dist-es/index.js contains:
  import "./commands";

Imported file ../../node_modules/.pnpm/@aws-sdk+client-dynamodb@3.749.0/node_modules/@aws-sdk/client-dynamodb/dist-es/commands/index.js contains:
  import "./DescribeKinesisStreamingDestinationCommand";

So imported file ../../node_modules/.pnpm/@aws-sdk+client-dynamodb@3.749.0/node_modules/@aws-sdk/client-dynamodb/dist-es/commands/DescribeKinesisStreamingDestinationCommand.js is included in the bundle.

Expected Behavior

Only the commands used should be included in the bundle.

Possible Solution

When importing the S3 client directly, via using the path directly, the issue goes away. However, this is impractical for the DynamoDB client, as we'd need to do the same for every command import that we use.

Additional Information/Context

This issue is based on taking the recommendations mentioned in #3542 already into account.

@spaceemotion spaceemotion added bug This issue is a bug. needs-triage This issue or PR still needs to be triaged. labels Feb 16, 2025
@github-actions github-actions bot added the potential-regression Marking this issue as a potential regression to be checked by team member label Feb 16, 2025
@spaceemotion
Copy link
Author

spaceemotion commented Feb 16, 2025

After a bit more digging, I wrote a custom esbuild plugin that only tries to include the commands we actually need. However, there are still a couple files that don't seem to be necessarily included in full?

{
  name: 'exclude-aggregated clients',
  setup(build) {
    build.onLoad({ filter: /@aws-sdk\/client-dynamodb\/dist-es\/DynamoDB\.js$/ }, () => {
      return {
        contents: 'export default {};',
        loader: 'js',
      };
    });

    build.onLoad({ filter: /@aws-sdk\/client-dynamodb\/dist-es\/commands\/index\.js$/ }, () => {
      const commands = [
        'BatchGetItemCommand',
        'BatchWriteItemCommand',
        'GetItemCommand',
        'PutItemCommand',
        'UpdateItemCommand',
        'DeleteItemCommand',
        'QueryCommand',
      ];

      return {
        contents: commands.map((command) => `export * from './${command}';`).join('\n'),
        loader: 'js',
      };
    });

    build.onLoad({ filter: /@aws-sdk\/client-s3\/dist-es\/S3\.js$/ }, () => {
      return {
        contents: 'export default {};',
        loader: 'js',
      };
    });
  },
}

There's a large AWS_json file that gets included by various commands, but for some reason it's not just pulling in the ones needed (the import sits at 33kb)

Imported file src/services/dynamoDbClient.ts contains:
  import "@aws-sdk/client-dynamodb/dist-es/DynamoDBClient.js";

Imported file ../../node_modules/.pnpm/@aws-sdk+client-dynamodb@3.749.0/node_modules/@aws-sdk/client-dynamodb/dist-es/DynamoDBClient.js contains:
  import "./commands/DescribeEndpointsCommand";

Imported file ../../node_modules/.pnpm/@aws-sdk+client-dynamodb@3.749.0/node_modules/@aws-sdk/client-dynamodb/dist-es/commands/DescribeEndpointsCommand.js contains:
  import "../protocols/Aws_json1_0";

So imported file ../../node_modules/.pnpm/@aws-sdk+client-dynamodb@3.749.0/node_modules/@aws-sdk/client-dynamodb/dist-es/protocols/Aws_json1_0.js is included in the bundle.

@spaceemotion
Copy link
Author

I also asked over at the esbuild project and got a reply that this is indeed something that AWS has to fix: evanw/esbuild#1698 (comment)

@kuhe
Copy link
Contributor

kuhe commented Feb 19, 2025

When I run this I only get one Command in the bundle

// index.ts
export { DynamoDBClient, GetItemCommand } from "@aws-sdk/client-dynamodb";
	npx esbuild --bundle index.ts --outfile=dist/bundle.js --format=esm \
		--main-fields=module,main --platform=node  \
		--external:@aws-sdk/client-sts \
		--external:@aws-sdk/client-sso* \
		--external:@aws-sdk/credential-provider-* \
		--external:@aws-sdk/token-providers \
		--external:fast-xml-parser

esbuild 0.21.4

@spaceemotion
Copy link
Author

spaceemotion commented Feb 19, 2025

@kuhe Thanks for the test! Your code does work as expected (well, it does pull in one extra command, but that's fine). Is there a reason why you made those specific packages external? From the AWS blogs and such I read that the AWS SDK should be included in the bundle - or are there exceptions for specific packages?

I think I have found the culprit of my issue though, and it looks like I caught a weird case: In the lambda code there's an async import, which itself pulls in only one exported function (+ client) from the dynamodb service file I have.

As soon as I added that async boundary to my test file, the tree shaking stopped working. Once I import the files like normal, only the necessary bits get included. I'll ask Evan if that's expected behavior.


Edit: Here's a test case that pulls everything in:

// test.ts
export const migrations = {
  'add-users': import('./migrations/20250101-add-users.ts'),
};
// 20250101-add-users.ts
import { QueryCommand } from '@aws-sdk/client-dynamodb';

export default function migrate() {
  new QueryCommand();
}

@kuhe
Copy link
Contributor

kuhe commented Feb 19, 2025

I externalized some packages because they are part of the AWS SDK default credential provider chain that may be unnecessary in Lambda. That part is optional and shouldn't affect the tree-shaking.

@zshzbh zshzbh removed the needs-triage This issue or PR still needs to be triaged. label Feb 20, 2025
@zshzbh zshzbh added the p2 This is a standard priority issue label Feb 20, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug This issue is a bug. p2 This is a standard priority issue potential-regression Marking this issue as a potential regression to be checked by team member
Projects
None yet
Development

No branches or pull requests

3 participants