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

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

Merged
merged 9 commits into from May 13, 2024
Merged

Conversation

kuhe
Copy link
Contributor

@kuhe kuhe commented May 8, 2024

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.

  • existing code in @aws-sdk/core has been moved into submodules
  • top level exports were preserved for backwards compatibility, but are no longer necessary in @aws-sdk/core.
  • automatic linting for correct metadata declarations when adding new core submodules
  • submodule cross-imports are possible

Testing

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

Checklist

  • e2e testing
  • cucumber-js

@kuhe kuhe requested a review from a team as a code owner May 8, 2024 20:11
@kuhe
Copy link
Contributor Author

kuhe commented May 9, 2024

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
Copy link
Contributor

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
Copy link
Contributor Author

kuhe commented May 13, 2024

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

@kuhe kuhe merged commit 31ce1e9 into aws:main May 13, 2024
5 checks passed
@kuhe kuhe deleted the chore/submodules branch May 13, 2024 20:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants