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

fix: aws-cdk-lib imports from ESM modules are broken #23846

Merged
merged 13 commits into from
Jan 26, 2023
3 changes: 3 additions & 0 deletions packages/aws-cdk-lib/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
!NOTICE
!README.md
!scripts/
!scripts/*.ts
!scripts/*.sh
scripts/*.d.ts

.LAST_BUILD
*.snk
Expand Down
2 changes: 2 additions & 0 deletions packages/aws-cdk-lib/.npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,5 @@ junit.xml
exports.d.ts
exports.js
exports.js.map

scripts
6 changes: 4 additions & 2 deletions packages/aws-cdk-lib/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@
"cp exports.js index.js",
"node ./scripts/verify-imports-resolve-same.js",
"node ./scripts/verify-imports-shielded.js",
"/bin/bash ./scripts/minify-sources.sh"
"/bin/bash ./scripts/minify-sources.sh",
"node ./scripts/verify-esm-exports.js"
]
},
"cdk-package": {
Expand Down Expand Up @@ -384,7 +385,8 @@
"esbuild": "^0.16.16",
"fs-extra": "^9.1.0",
"ts-node": "^9.1.1",
"typescript": "~3.8.3"
"typescript": "~3.8.3",
"cjs-module-lexer": "^1.2.2"
},
"peerDependencies": {
"constructs": "^10.0.0"
Expand Down
2 changes: 1 addition & 1 deletion packages/aws-cdk-lib/scripts/minify-sources.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
scriptdir=$(cd $(dirname $0) && pwd)
cd ${scriptdir}/..

find . -name '*.js' ! -name 'exports.js' ! -name '.eslintrc.js' ! -path '*node_modules*' | xargs npx esbuild \
find . -name '*.js' ! -name 'index.js' ! -name 'exports.js' ! -name '.eslintrc.js' ! -path '*node_modules*' | xargs npx esbuild \
--sourcemap \
--platform=node \
--format=cjs \
Expand Down
28 changes: 28 additions & 0 deletions packages/aws-cdk-lib/scripts/verify-esm-exports.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/* eslint-disable import/no-extraneous-dependencies */
// This script is executed post packaging to verify that the JS trickery we do to make
// lazy exports show up in an ESM import actually works.
//
import * as console from 'console';
import * as lexer from 'cjs-module-lexer';
import * as fs from 'fs-extra';

async function main() {
console.log('🧐 Checking the CJS exports for index.js...');

const indexJs = await fs.readFile('index.js', { encoding: 'utf-8' });

const result = lexer.parse(indexJs);

if (!result.exports.includes('aws_cognito')) {
throw new Error('The lexer did not find aws_cognito in the set of exports of index.js');
}
if (!result.reexports.includes('./core')) {
throw new Error('The lexer did not find ./core in the set of -reexports of index.js');
}
}

main().catch((err) => {
console.error(err);
process.exitCode = 1;
});

41 changes: 39 additions & 2 deletions tools/@aws-cdk/ubergen/bin/ubergen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,13 +299,16 @@ async function prepareSourceFiles(libraries: readonly LibraryReference[], packag
indexStatements.push(`export * from './${library.shortName}';`);
exportsStatements.unshift(`export * from './${library.shortName}';`);
} else {
indexStatements.push(`export * as ${library.shortName.replace(/-/g, '_')} from './${library.shortName}';`);
exportsStatements.push(`Object.defineProperty(exports, '${library.shortName.replace(/-/g, '_')}', { get: function () { return require('./${library.shortName}'); } });`);
const exportName = library.shortName.replace(/-/g, '_');

indexStatements.push(`export * as ${exportName} from './${library.shortName}';`);
addLazyExport(exportsStatements, `./${library.shortName}`, exportName);
}
copySubmoduleExports(packageJson.exports, library, library.shortName);
}

// make the exports.ts file pass linting
exportsStatements.unshift('const DUMMY = undefined;');
exportsStatements.unshift('/* eslint-disable @typescript-eslint/no-require-imports */');

await fs.writeFile(path.join(libRoot, 'index.ts'), indexStatements.join('\n'), { encoding: 'utf8' });
Expand All @@ -314,6 +317,40 @@ async function prepareSourceFiles(libraries: readonly LibraryReference[], packag
console.log('\t🍺 Success!');
}


/**
* Make this module available under the given exportName, but make it lazily loaded
*
* If we don't do this, Node is going to load all submodules upon import (even if you don't use
* any of their contents), which takes a long time.
*
* We need to do a little hack here to support this export style from ESM modules. The
* ESM loader will lex any imported CJS files to detect named exports using a NPM package
* called `cjs-module-lexer` (source: https://nodejs.org/api/packages.html#modules-loaders). This
* lexer tries to avoid recognizing exports that may have side effects, and it will refuse to
* recognize:
*
* Object.defineProperty(..., 'myExport', get: () => require('./module'))
*
* Because we know our exports are safe and side-effect free, we trick it by hiding the
* name in a string concatenation, and then add a plain export of something it WILL
* recognize, but make sure it's a no-op:
*
* Object.defineProperty(..., 'my' + 'Export', get: () => require('./module'))
* if (false) {
* exports.myExport = DUMMY;
* }
*
* The effect is the same but we bypass the lexer and everything works out at runtime.
*/
function addLazyExport(into: string[], moduleName: string, exportName: string) {
const firstChar = exportName.substr(0, 1);
const rest = exportName.substr(1);

into.push(`Object.defineProperty(exports, '${firstChar}' + '${rest}', { get: function () { return require('${moduleName}'); } });`);
into.push(`if (false) { exports.${exportName} = DUMMY; }`);
}

/**
* Copy the sublibrary's exports into the 'exports' of the main library.
*
Expand Down
2 changes: 1 addition & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3411,7 +3411,7 @@ cint@^8.2.1:
resolved "https://registry.npmjs.org/cint/-/cint-8.2.1.tgz#70386b1b48e2773d0d63166a55aff94ef4456a12"
integrity sha512-gyWqJHXgDFPNx7PEyFJotutav+al92TTC3dWlMFyTETlOyKBQMZb7Cetqmj3GlrnSILHwSJRwf4mIGzc7C5lXw==

cjs-module-lexer@^1.0.0:
cjs-module-lexer@^1.0.0, cjs-module-lexer@^1.2.2:
version "1.2.2"
resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40"
integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==
Expand Down