Skip to content

[Bug?]: PNP with ESM using Lambda Layers is broken with AWS SAM #6604

@Downchuck

Description

@Downchuck

Self-service

  • [?] I'd be willing to implement a fix

Describe the bug

This is very specific to AWS SAM images - there may be some yarn bugs its uncovering, but to date this only comes up in a SAM image.
As far as I can tell, AWS has expressed negative interest in Yarn PNP and further they have their own /var/runtime/index.mjs wrapper which complicates things.

While the .pnp.cjs file works well in AWS SAM, the ESM loader does not seem to work as desired when the loader or dependencies are outside of the project folder. Configuration variables such as the yarn cache folder are not used and it uses the current cwd, making it difficult to point to a .cjs file (which the .mjs loader uses).

Here is the fix I'm using - as the dependencies are in /opt/nodejs/ -e-g- ".yarn" for the cache folder and family, and ".pnp.cjs" for the cjs file which is used by the .pnp.loader.mjs file.

    "hot": "NODE_OPTIONS=\"--loader /var/task/loader.mjs\" sam local start-api --debug --disable-authorizer --skip-pull-image",

loader.mjs:

/*
- Our .pnp.loader.mjs uses the parentURL, to try to pickup .pnp.cjs which fails.
- It does not seem to use any of our env variables to set the yarn cache either.
- Pretend we are in /opt/nodejs/ so it picks up .pnp.cjs and the .yarn folder.
*/

const { default: pnp } = await import("/opt/nodejs/.pnp.cjs");
pnp.setup();

const fs = await import("node:fs");

const esmpnp = await import("/opt/nodejs/.pnp.loader.mjs");
export async function resolve(specifier, context, nextResolve) {
  const parentURL = context.parentURL?.replace("file:///var/task/", "file:///opt/nodejs/");
  const contexts = { ...context, parentURL };
  const resolve = await esmpnp.resolve(specifier, specifier.startsWith("./") ? context : contexts, nextResolve);
  // console.log({specifier, resolve});
  return resolve;
}

export async function load(urlString, context, nextLoad) {
  const load = await esmpnp.load(urlString, context, nextLoad);
  // Node ESM will wrap the CJS loader which does not use the monkey patched fs reader.
  if(load.format === 'commonjs') {
    if(urlString.startsWith("file:///opt/nodejs/.yarn/cache/")) {
      load.source = fs.readFileSync(urlString.slice("file://".length));
    }
  }
  return load;
}

To reproduce

Launch an ESM project putting the Yarn cache folder into a Layer Zip.
This will wind up in /opt/nodejs/.yarn.

The ESM Loader will not work well with the /opt/nodejs folder as it is run from /var/task regardless of location. It may have a struggling finding the /opt/nodejs/.pnp.cjs file - which instead has to go into /var/task - but working around from that, the folder search path will still look in /var/task/.yarn and not /opt/nodejs/.yarn despite every attempt I could muster.

It's odd that yarn is not using any of the configuration variables for the .pnp.loader.mjs but with such a strict environment it's relatively unimportant as long as things just work.

Note in the exact same setup, the ".pnp.cjs" registration does work well for ".cjs" files.

          NODE_OPTIONS: -r /opt/nodejs/.pnp.cjs --loader /opt/nodejs/.pnp.loader.mjs
          # Switch to workaround
          # NODE_OPTIONS: --loader /var/task/hack.mjs
          YARN_CACHE_FOLDER: /opt/nodejs/.yarn/cache
          YARN_PNP_UNPLUGGED_FOLDER: /opt/nodejs/.yarn/unplugged
          YARN_VIRTUAL_FOLDER: /opt/nodejs/.yarn/cache
          YARN_INSTALL_STATE_PATH: /opt/nodejs/.yarn/install-state.gz
          YARN_PATCH_FOLDER: /opt/nodejs/.yarn/patches
          YARN_PNP_MODE: loose

Environment

This is using Yarn PNP 4.5.x  ESM and CJS loaders on the latest Lambda Node Image:

samcli/lambda-nodejs:20-x86_64-18095d962f12f8cd56fd3db91

Additional context

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions