Skip to content

Commit 8aaeffb

Browse files
authored
fix: importing aws-cdk-lib is slow (#23813)
Fixes slow import of top-level aws-cdk-lib. At the very end of the build, the compiled `index.js` file is replaced with a version optimized for import performance. Instead of loading submodules directly at the top level, exports are defined as getter functions. This way they will only be required when actually imported from `aws-cdk-lib`. Improves AWS CDK app performance by ~400ms. Background reading: https://medium.com/trabe/exporting-getters-in-commonjs-modules-dd8f98b7d85e ---- ```console $ hyperfine -i "node -e \"const { App } = require('aws-cdk-lib');\"" "node -e \"const { App } = require('aws-cdk-lib-candidate');\"" --warmup 5 Benchmark 1: node -e "const { App } = require('aws-cdk-lib');" Time (mean ± σ): 1.227 s ± 0.125 s [User: 1.102 s, System: 0.262 s] Range (min … max): 1.110 s … 1.508 s 10 runs Benchmark 2: node -e "const { App } = require('aws-cdk-lib-candidate');" Time (mean ± σ): 251.9 ms ± 19.7 ms [User: 226.6 ms, System: 51.0 ms] Range (min … max): 224.6 ms … 278.3 ms 10 runs Summary 'node -e "const { App } = require('aws-cdk-lib-candidate');"' ran 4.87 ± 0.62 times faster than 'node -e "const { App } = require('aws-cdk-lib');"' ``` ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) ### Adding new Construct Runtime Dependencies: * [ ] This PR adds new construct runtime dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-construct-runtime-dependencies) ### New Features * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)? * [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 134d624 commit 8aaeffb

File tree

4 files changed

+20
-1
lines changed

4 files changed

+20
-1
lines changed

packages/aws-cdk-lib/.npmignore

+5
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,8 @@ junit.xml
3131

3232
# exclude source maps as they only work locally
3333
*.map
34+
35+
# exclude tempory export files
36+
exports.d.ts
37+
exports.js
38+
exports.js.map

packages/aws-cdk-lib/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"stripDeprecated": true,
3939
"compressAssembly": true,
4040
"post": [
41+
"cp exports.js index.js",
4142
"node ./scripts/verify-imports-resolve-same.js",
4243
"node ./scripts/verify-imports-shielded.js",
4344
"/bin/bash ./scripts/minify-sources.sh"

packages/aws-cdk-lib/scripts/minify-sources.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
scriptdir=$(cd $(dirname $0) && pwd)
1717
cd ${scriptdir}/..
1818

19-
find . -name '*.js' ! -name '.eslintrc.js' ! -path '*node_modules*' | xargs npx esbuild \
19+
find . -name '*.js' ! -name 'exports.js' ! -name '.eslintrc.js' ! -path '*node_modules*' | xargs npx esbuild \
2020
--sourcemap \
2121
--platform=node \
2222
--format=cjs \

tools/@aws-cdk/ubergen/bin/ubergen.ts

+13
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,14 @@ async function prepareSourceFiles(libraries: readonly LibraryReference[], packag
280280
'./.warnings.jsii.js': './.warnings.jsii.js',
281281
};
282282

283+
// We use the index.ts to compile type definitions.
284+
// At the very end we replace the compiled index.js file with our fixed version exports.js.
285+
// exports.js has the top level submodules exports defined as a getter function,
286+
// so they are not automatically loaded when importing from `aws-cdk-lib`.
287+
// This improves AWS CDK app performance by ~400ms.
283288
const indexStatements = new Array<string>();
289+
const exportsStatements = new Array<string>();
290+
284291
for (const library of libraries) {
285292
const libDir = path.join(libRoot, library.shortName);
286293
const copied = await transformPackage(library, packageJson, libDir, libraries);
@@ -290,13 +297,19 @@ async function prepareSourceFiles(libraries: readonly LibraryReference[], packag
290297
}
291298
if (library.shortName === 'core') {
292299
indexStatements.push(`export * from './${library.shortName}';`);
300+
exportsStatements.unshift(`export * from './${library.shortName}';`);
293301
} else {
294302
indexStatements.push(`export * as ${library.shortName.replace(/-/g, '_')} from './${library.shortName}';`);
303+
exportsStatements.push(`Object.defineProperty(exports, '${library.shortName.replace(/-/g, '_')}', { get: function () { return require('./${library.shortName}'); } });`);
295304
}
296305
copySubmoduleExports(packageJson.exports, library, library.shortName);
297306
}
298307

308+
// make the exports.ts file pass linting
309+
exportsStatements.unshift('/* eslint-disable @typescript-eslint/no-require-imports */');
310+
299311
await fs.writeFile(path.join(libRoot, 'index.ts'), indexStatements.join('\n'), { encoding: 'utf8' });
312+
await fs.writeFile(path.join(libRoot, 'exports.ts'), exportsStatements.join('\n'), { encoding: 'utf8' });
300313

301314
console.log('\t🍺 Success!');
302315
}

0 commit comments

Comments
 (0)