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

Cannot redefine property: handle when run as AWS lambda #2199

Closed
Ancient-Dragon opened this issue Apr 20, 2022 · 7 comments
Closed

Cannot redefine property: handle when run as AWS lambda #2199

Ancient-Dragon opened this issue Apr 20, 2022 · 7 comments

Comments

@Ancient-Dragon
Copy link

Hi there,

I'm trying to use esbuild to bundle my typescript project to be used by aws lambda. I'm not sure what else to try, but I cannot get it to successfully pass while using es6 exports in my code.

My code looks something like:

export const handle = async (event: any) => {
    console.log(event);
    // ...
}

My esbuild.config.js looks something like this:

const esbuild = require('esbuild');

esbuild.build({
  entryPoints: ['src/index.ts'],
  bundle: true,
  platform: 'node',
  target: 'node14',
  outdir: './bundle',
  tsconfig: './tsconfig.json',
  minify: false,
  format: 'cjs',
  keepNames: false,
  treeShaking: true,
  sourcemap: 'inline',
}).catch(() => process.exit(1));

and my ts config looks like this:

{
  "compilerOptions": {
    "target": "es2020",
    "module": "commonjs",
    "outDir": "dist",
    "rootDir": "src",
    "typeRoots": [
      "node_modules/@types",
      "src/@types"
    ],
    "lib": [
      "es2020"
    ],
    "allowJs": true,
    "allowSyntheticDefaultImports": true,
    "alwaysStrict": true,
    "declaration": false,
    "emitDecoratorMetadata": true,
    "esModuleInterop": true,
    "experimentalDecorators": true,
    "forceConsistentCasingInFileNames": true,
    "incremental": false,
    "inlineSourceMap": true,
    "inlineSources": true,
    "moduleResolution": "node",
    "noFallthroughCasesInSwitch": false,
    "noImplicitAny": true,
    "noImplicitReturns": true,
    "noImplicitThis": true,
    "noUnusedLocals": false,
    "noUnusedParameters": true,
    "preserveConstEnums": true,
    "removeComments": true,
    "resolveJsonModule": true,
    "strictNullChecks": true,
    "strictPropertyInitialization": false,
    "skipLibCheck": true,
    "strict": true
  },
  "include": [
    "src/**/*.ts",
    "node_modules/*"
  ]
}

The only way I can get this to work is instead of using export const handle / export default { handle }. I use exports.handle = handle. Any help would be greatly appreciated

@evanw
Copy link
Owner

evanw commented Apr 20, 2022

I can't reproduce this by bundling that code with those options. This isn't surprising because the code you provided just exports something without using it. I assume you also have some additional code that is trying to import handle and then mutate it? I'm assuming that some code does something like this:

require('./bundle/index.js').handle = something;

That won't work because the export keyword creates an immutable export alias in JavaScript. CommonJS exports are mutable, however, so if you need mutable exports then using CommonJS-style exports.handle = handle instead of the export keyword is a reasonable solution.

Another potential resolution is to investigate what is attempting to mutate the handle export in the first place, and just removing that code if the mutation is unnecessary.

@Ancient-Dragon
Copy link
Author

Ancient-Dragon commented Apr 21, 2022

So when writing for lambda you expose a function, but don't actually use it in your code. When you deploy the compiled js to lambda aws calls it upon the lambda being triggered. Nothing in my code (outside of tests) calls the method and nothing mutates the export.

@evanw
Copy link
Owner

evanw commented Apr 21, 2022

Ah, ok. Sorry I am not familiar with AWS Lambda. Maybe the problem is in AWS Lambda itself then? Does the stack trace that you get perhaps provide more information? What does it look like?

Edit: Adding the unactionable label because the problem can't be identified with the information given here.

@evanw evanw changed the title Cannot redefine property: handle Cannot redefine property: handle when run as AWS lambda Apr 22, 2022
@Ancient-Dragon
Copy link
Author

Here is the error I get:

Cannot redefine property: handle
errorType | TypeError
TypeError: Cannot redefine property: handle
at Function.defineProperty (<anonymous>)
at defineProperty (/opt/nodejs/node_modules/shimmer/index.js:14:10)
at AwsLambdaInstrumentation.wrap [as _wrap] (/opt/nodejs/node_modules/shimmer/index.js:56:3)
at InstrumentationNodeModuleFile.patch (/opt/nodejs/node_modules/@opentelemetry/instrumentation-aws-lambda/build/src/instrumentation.js:69:26)
at AwsLambdaInstrumentation._onRequire (/opt/nodejs/node_modules/@opentelemetry/instrumentation/build/src/platform/node/instrumentation.js:85:33)
at /opt/nodejs/node_modules/@opentelemetry/instrumentation/build/src/platform/node/instrumentation.js:112:29
at Module.Hook._require.Module.require (/opt/nodejs/node_modules/require-in-the-middle/index.js:154:32)
at Module.Hook._require.Module.require (/opt/nodejs/node_modules/require-in-the-middle/index.js:80:39)
at Module.Hook._require.Module.require (/opt/nodejs/node_modules/require-in-the-middle/index.js:80:39)
at Module.Hook._require.Module.require (/opt/nodejs/node_modules/require-in-the-middle/index.js:80:39)

I'm not sure why but for some reason it seems to export twice in esbuild (although this could be my lack of esbuild knowledge)

// src/index.ts
var src_exports = {};
__export(src_exports, {
  handle: () => handle
});
// ...
0 && (module.exports = {
  handle
});

A similar function compiled using tsc:

Object.defineProperty(exports, "__esModule", { value: true });
exports.handler = void 0;
//...
async function handler(event) {
   //...
}
exports.handler = handler;

@hyrious
Copy link

hyrious commented Apr 22, 2022

0 && (module.exports ... does nothing, it exists for some static code lexer (like cjs-module-lexer, used by node.js) to guess named exports from commonjs sources.

AWS lambda seems to use shimmer which uses defineProperty to monkey-patch cjs modules to make them runnable in their special environment. However to keep the esm semantic esbuild marks these exports as immutable, which prevents doing these kind of work.

Currently you can only use cjs style exports directly (exports.name = ...) to make them mutable.

There was a same request before #1079.

@Ancient-Dragon
Copy link
Author

Thanks for the explanation on cjs :)

I assume by the fact that it's been open for a while that feature isn't going to be implemented so I should just continue to use exports.handle or speak with aws about the use of shimmer?

@evanw
Copy link
Owner

evanw commented May 7, 2022

Yes, this is unlikely to be implemented. I recommend just using exports.handle since that appears to be the input format that the AWS API expects.

Closing this issue as "by design."

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants