aws / aws-sdk-js-v3

Modularized AWS SDK for JavaScript.
Apache License 2.0
2.96k stars 557 forks source link

chore(core): create submodule exports in core #6079

Closed kuhe closed 1 month ago

kuhe commented 1 month ago

This PR modifies @aws-sdk/core into a special package that exports submodules using the package.json exports field, available since Node.js 12 and widely supported by bundlers.

These submodules preserve the benefits of modularization while also consolidating package sprawl and making it easier to have a consistent single version of core functionality.

Testing

internal test code available at commit AwsSdkJavaScriptTest/trees/5be19fdc2a1e0d71515f2a081020478e44fb5f5a

Checklist

kuhe commented 1 month ago

Submodules examples:

Submodule source code, authoring submodules

./packages/core/src/submodules/addition/add.ts

export const add = (a, b) => a + b;

./packages/core/src/submodules/addition/index.ts

export * from "./add";

Submodule source code, cross-importing submodules

./packages/core/src/submodules/multiplication/multiply.ts

// scope/pkg/submodule is used even within the same package.
import { add } from "@aws-sdk/core/addition";

export const multiply = (a, b) => {
   let p = 0;
   while (b--) p = add(p, a);
   return p;
}

./packages/core/src/submodules/multiplication/index.ts

export * from "./multiply";

Submodule dist-cjs, dist-es file layout

./packages/core
  dist-cjs/
    submodules/
      addition/
        index.js # submodule index
      multiplication/
        index.js # submodule index
    index.js # deprecated root index
  dist-es/
    submodules/
      addition/
        add.js
        index.js
      multiplication/
        multiply.js
        index.js
    index.js

Submodules have one js file per submodule for dist-cjs as a prebundle artifact for Node.js initialization time optimization. Because they use canonical @scope/pkg/submodule imports, no redundancies are included in each bundle. They function equivalently as separate packages would.

In dist-types and dist-es, each source file continues to have a corresponding dist file. This allows ESM bundlers and TypeScript to continue working as expected w.r.t. tree-shaking etc.

Submodule downstream consumer code

Consumer code can seamlessly treat submodules the same as different packages.

import { add } from "@aws-sdk/core/addition";
import { multiply } from "@aws-sdk/core/multiplication";

export const power = (a, b) => {
  let p = 1;
  while (b--) p = multiply(p, a);
  return p;
}

Submodule metadata within @aws-sdk/core

The linter will take care of this automatically. Each module needs the following metadata:

./package.json

{
  "exports": {
    "./MODULE_NAME": {
      "node": "./dist-cjs/submodules/MODULE_NAME/index.js",
      "import": "./dist-es/submodules/MODULE_NAME/index.js",
      "require": "./dist-cjs/submodules/MODULE_NAME/index.js",
      "types": "./dist-types/submodules/MODULE_NAME/index.d.ts"
    },
  }
}

For import mapping.

(add to files: []).

./tsconfig.X.json

{
  "compilerOptions": {
    "paths": {
      "@aws-sdk/core/SUBMODULE_NAME": ["./src/submodules/SUBMODULE_NAME/index.ts"],
    }
  }
}

For cross-submodule imports within the @aws-sdk/core package.

Compatibility

For an application that does not understand the exports field, the linter will generate a compatibility redirect file.

./addition.js

/**
 * Do not edit:
 * This is a compatibility redirect for contexts that do not understand package.json exports field.
 */
module.exports = require("./dist-cjs/submodules/addition/index.js");

The location of this file is in the package root to allow an identical resolution path to actual submodules.

everett1992 commented 1 month ago

I don't know if any authoritative definitions for the different export conditions, so I don't if it's 'okay' to use import for code that doesn't include file extensions. I know that dist-es won't work in node, but node will use prefer the node condition and load dist-cjs.

module is an alternative condition you could use, I know that webpack and esbuild will use module, and that node won't.

kuhe commented 1 month ago

confirmed works as expected in latest webpack, rollup, esbuild, vite, react-native (metro), and Node.js (CJS & MJS)

github-actions[bot] commented 1 month ago

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs and link to relevant comments in this thread.