Dynamically importing @aws-sdk client modules
henhal opened this issue · 1 comments
My application has a dependency to a library module, which in turn has @aws-sdk/client-s3
as a peerDependency
, and uses a dynamic import to import it, since that functionality is optional.
My application then has a dependency to @aws-sdk/client-s3
.
When bundling with serverless-bundle without any extra config steps, unsurprisingly @aws-sdk/client-s3
is not bundled due to not being statically referenced anywhere, so I add the following:
bundle:
forceInclude:
- "@aws-sdk/client-s3"
This creates a zip containing
src
my-function
index.js
node_modules
@aws-sdk
client-s3
< a bunch of sub-dependencies of client-s3 >
So far so good, but the library is still not able to load @aws-sdk/client-s3
in runtime:
const {S3} = await import('@aws-sdk/client-s3');
const s3 = new S3({region: 'eu-west-1'});
This produces an error like this:
error: webpack:/// < ... > :2\n\tvar e = new Error(\"Cannot find module '\" + req + \"'\");\n\t ^\n\nError: Cannot find module '@aws-sdk/client-s3'\n
I'm not a very experienced webpack user but I love using serverless-bundle with it's almost-zero config, but when encountering this I realize I don't understand how webpack performs module resolution.
Is there something else I need to add? The README specifically has an example of how to deal with a dynamically imported module using forceInclude
. So how come the module is included in node_modules
but not resolved?
To be clear: dynamically importing the @aws-sdk/client-s3 lib from my app works when forceInclude
is present, but dynamically importing it from the lib does not. So to me it seems to be about the path to node_modules
that the lib uses. While all the code in my-lib is bundled into the single index.js
file, calls to await import('@aws-sdk/client-s3')
only works from code that was originally in my-app, and not in my-lib.
For clarification, the setup is something like this:
my-lib:
package.json:
"devDependencies": {
"@aws-sdk/client-s3": "^3.83.0"
},
"peerDependencies": {
"@aws-sdk/client-s3": "^3.83.0"
}
index.ts
export async function testS3fromLib() {
const {S3} = await import('@aws-sdk/client-s3');
return new S3({region: 'eu-west-1'});
}
my-app:
package.json:
"dependencies": {
"my-lib": "1.0.0",
"@aws-sdk/client-s3": "^3.83.0"
}
index.ts:
import {testS3fromLib} from 'my-lib';
testS3fromLib().then(console.log, console.log); // prints 'Cannot find module' error from code in my-lib
async function testS3FromApp() {
const {S3} = await import('@aws-sdk/client-s3');
return new S3({region: 'eu-west-1'});
}
testS3FromApp().then(console.log, console.log); // works, prints s3 object instance
I had quite some trouble fixing this, but I ended up with not having to use forceInclude
if my dynamic imports were distinct enough.
At first I had code that imported a few different AWS SDK clients semi-dynamically through a utility function with something like await import('@aws-sdk/' + clientName)
but this caused webpack to package all files matching @aws-sdk
, and I mean all files; I got lots of errors about webpack not having any loader for .md
files, .d.ts
files etc. This had be stumped for a full day before realizing that if I make my dynamic imports "static enough" by not building the import path in runtime at all, it would include only the modules I need (if they exist) and as a side-effect not include non-TS/JS files such as .md
etc.
I don't understand why having a wildcard import like '@aws/sdk' + myClient
makes webpack try to package README.md files etc, but at least I got it working by narrowing my dynamic imports down.
But perhaps there's a new default config to add to serverless-bundle to make webpack only include *.js
and *.ts
(but exclude .d.ts
)?
Anyway, I got this working with peerDependencies
and a dynamic import if the dynamic import has a string literal for the full import path, i.e., known in compile time.