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

feat: generate barrel entrypoints for cjs / node module resolutions #435

Open
juliusmarminge opened this issue Jan 28, 2024 · 8 comments
Open

Comments

@juliusmarminge
Copy link
Contributor

juliusmarminge commented Jan 28, 2024

when using the node module resolution, it's "hard" to support multiple entrypoints. The main/module/types field in package json may point to a dist file, but any other entrypoints must be relative to pacakge root. ( this is solved in modern configurations using the package.json#exports that's supported with moduleResolution >= Node16 )

in trpc, we have this script that generates these entrypoints based on the package.json#exports field, for example:

given

{
  "main": "dist/index.js",
  "require": "dist/index.cjs",
  "types": "dist/index.d.ts",
  "exports": {
    ".": {
      "import": {
        "types": "./dist/index.d.ts",
        "default": "./dist/index.js"  
      },
      "require": {
        "types": "./dist/index.d.cts",
        "default": "./dist/index.cjs"
      }
    }
    "./foo": {
      "import": {
        "types": "./dist/foo.d.ts",
        "default": "./dist/foo.js"  
      },
      "require": {
        "types": "./dist/foo.d.cts",
        "default": "./dist/foo.cjs"
      }
    },
    "./bar": {
      "import": {
        "types": "./dist/bar.d.ts",
        "default": "./dist/bar.js"  
      },
      "require": {
        "types": "./dist/bar.d.cts",
        "default": "./dist/bar.cjs"
      }
    }
  }
}

the following files will be generated (see trpc bundled code here: https://www.npmjs.com/package/@trpc/server/v/11.0.0-next-beta.242?activeTab=code):

// foo/index.cjs
module.exports = require("../dist/foo.cjs");

// foo/index.d.ts
export * from "../dist/foo";

// bar/index.cjs
module.exports = require("../dist/bar.cjs");

// bar/index.d.ts
export * from "../dist/bar";

to support older module resolutions

what do you think of bunchee --prepare would generate these files?

@huozhi
Copy link
Owner

huozhi commented Jan 28, 2024

Yea that's possible. however it's also emitting the code change that might need to be added into .gitignore, also need to update "files" field in package.json.

Before we can handle that you can use sth like this to configure your package.json

  ".": {
      "import": {
        "types": "./dist/es/index.d.mts",
        "default": "./dist/es/index.mjs"
      },
      "require": {
        "types": "./dist/cjs/index.d.ts",
        "default": "./dist/cjs/index.js"
      }
    },
    "./subpath": {
      "import": {
        "types": "./subpath/index.d.mts",
        "default": "./subpath/index.mjs"
      },
      "require": {
        "types": "./subpath/index.d.ts",
        "default": "./subpath/index.js"
      }
    }

Then add subpath folder to .gitignore and "files" array of package.json. In this way all the esm & cjs output are in the subpath folder, including types which can support node10 module resolution

@juliusmarminge
Copy link
Contributor Author

Not sure i understand. Users should be able to import from like pkg/foo and pkg/bar etc no matter if their resolution can read package.json#exports.

I encountered this yesterday when consuming a package in expo

@huozhi
Copy link
Owner

huozhi commented Jan 28, 2024

If you look at the case of <pkg>/subpath in exports, you'll notice it's generating the dist files into ./pkg/subpath folder, which is able to let nodejs directly import the code from pkg/subpath/index.js when using node10 module resolution.

"./subpath": {
      "import": {
        "types": "./subpath/index.d.mts",
        "default": "./subpath/index.mjs"
      },
      "require": {
        "types": "./subpath/index.d.ts",
        "default": "./subpath/index.js"
      }
    }

It's able to extend to pkg/foo, pkg/bar or anything similar. Your source code is still in ./pkg/src, and the output bundles are in ./pkg/dist (for "." export or main), ./pkg/foo (for "./foo" export)

@juliusmarminge
Copy link
Contributor Author

I dont think thats correct. Node10 module resolution doesnt look at package.json#exports, so the files have to exist from root. This is not the case when files are bundled in ./dist/**

@juliusmarminge
Copy link
Contributor Author

Oh i see now you're not proposing bundling in dist...

@huozhi
Copy link
Owner

huozhi commented Jan 28, 2024

Yeah if the dist files are existing in subdirectory, and those can also be used for exports field to referring to, also directly accessing by directory path

@juliusmarminge
Copy link
Contributor Author

Yea this works like a charm and is likely a better way than just re-exporting dist. Gets a bit weird when you have an index entrypoint though, unless you wanna inline those files I guess...

Right now putting the index entry files in dist/ and the rest in their respective paths, for example:

CleanShot 2024-01-29 at 00 02 58

@juliusmarminge
Copy link
Contributor Author

juliusmarminge commented Jan 28, 2024

Still feel like there's some DX optimizations to be made here, since now it's a lot of stuff that must be setup each time I need to make changes here:

  1. Add the new entrypoints in package.json#exports so that bunchee knows to bundle it and where to put it
  2. Add the new folder to package.json.files
  3. Since we use turbo, add the new folder to the turbo.json#pipeline.build.outputs array.

The attached script i've written for trpc handles all of these, all I need to do is to add the new entrypoint in the input array. The gitignore is handled by a simple glob pattern that excludes all files and folders except the actual source:

# Ignore all folders except `src/` in every published package
packages/*/*/
!packages/*/src/
!packages/*/test/

Last step is obviously not related to bunchee, but it'd be cool if it could somehow perform some of these steps for me. bunchee --prepare will override all of my "custom" entrypoint paths, so I can't use that to auto-add new entrypoints:
CleanShot 2024-01-29 at 00 06 29

Could --prepare maybe read the current state and decide based on that what to write? i.e. in this case it'd see how my other entrpoints are defined and it would automatically add the new one in the same format instead of the default dist/es/<entry>.mjs and dist/cjs/<entry>.cjs etc? For the above example, adding a foo.ts and running --prepare would add

{
  "./foo": {
    "import": {
      "types": "./foo/index.d.mts",
      "default": "./foo/index.mjs",
    },
    "require": { ... }
  }
}

as well as adding foo to the files array?

@huozhi huozhi added the area:dx label Feb 5, 2024
@huozhi huozhi modified the milestones: v4, v5 Mar 10, 2024
@huozhi huozhi removed this from the v5 milestone Mar 20, 2024
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

2 participants