nx icon indicating copy to clipboard operation
nx copied to clipboard

In node with esbuild and esm, the output results do not include the libs

Open wizardnet972 opened this issue 2 years ago • 7 comments

Current Behavior

I create a nodejs app with esbuild and esm.

In the project, I have imports from libs.

When nx builds the node project it not add the mapping code to the output files (_resolveFilename) or don't add them to package.json.

The results are the libs is missing from the node runtime.

Expected Behavior

like cjs format, should be handle the imports from the libs.

GitHub Repo

No response

Steps to Reproduce

  1. create workspace
  2. create node app with esbuild
  3. change the format to esm

Nx Report

yarn nx report
yarn run v1.22.19
>  NX  Falling back to ts-node for local typescript execution. This may be a little slower.
  - To fix this, ensure @swc-node/register and @swc/core have been installed

 >  NX   Report complete - copy this into the issue template

   Node   : 18.13.0
   OS     : darwin arm64
   yarn   : 1.22.19
   Hasher : Native

   nx                 : 16.2.1
   @nx/js             : 16.2.1
   @nx/jest           : 16.2.1
   @nx/linter         : 16.2.1
   @nx/workspace      : 16.2.1
   @nx/devkit         : 16.2.1
   @nx/esbuild        : 16.2.1
   @nx/eslint-plugin  : 16.2.1
   @nx/node           : 16.2.1
   @nrwl/tao          : 16.2.1
   @nx/vite           : 16.2.1
   typescript         : 5.0.4
   ---------------------------------------
   Community plugins:
   @nx/rspack : 16.1.2

✨  Done in 1.10s.


### Failure Logs

_No response_

### Operating System

- [X] macOS
- [ ] Linux
- [ ] Windows
- [ ] Other (Please specify)

### Additional Information

_No response_

wizardnet972 avatar May 20 '23 13:05 wizardnet972

Another issue related to this, when I create @nx/js lib with esbuild, nx compile by default to esm and because the node is compile with cjs it can't import this lib. if I change the node app to work as esm then back to the main issue - the lib can't be found because esm doesn't handle the libs inside the project. also it not wrap them with __toEsm.

wizardnet972 avatar May 20 '23 13:05 wizardnet972

Hitting this same issue. Existed in 15.x too. Seems like there's a special case for cjs that handles lib imports. I assume there's a reason it's not enabled for esm?:

https://github.com/nrwl/nx/blob/master/packages/esbuild/src/executors/esbuild/lib/build-esbuild-options.ts#L69

halfbakedsneed avatar Jun 06 '23 04:06 halfbakedsneed

Another issue related to this, when I create @nx/js lib with esbuild, nx compile by default to esm and because the node is compile with cjs it can't import this lib. if I change the node app to work as esm then back to the main issue - the lib can't be found because esm doesn't handle the libs inside the project. also it not wrap them with __toEsm.

Currently you cannot import a @nx/js library inside a @nx/node app. You can verify this on an empty workspace by creating a node app and a js library both with esbuild as bundler and try to share code between them. @AgentEnder @jaysoo @FrozenPandaz @vsavkin

samratarmas avatar Jun 15 '23 23:06 samratarmas

@samratarmas and others with related problems:

I have am using shared @nx/js libs in my nx workspace right now in both vite/react front-end (esm) and node/express back-end (cjs) apps. The trick: build both cjs + esm so everything is happy.

I would prefer all esm but as you have discovered node generator and nx serve (among others...) breaks with esm and nx has not addressed these issues to date.

My solution is as follows:

context: all of my projects libs are buildable my "build" target in project.json uses the @nx/esbuild:esbuild executor

  • in package.json of your lib strip module etc so that instead of you overriding his value it will be generated during build
    • the package.json of my shared libs only specify name, version, and sideEffects
  • in project.json under targets.build.options
    • set "generatePackageJson": true
    • set "format": ["esm", "cjs"]

Note how project.json can override your tsconfig. You can even go all-out and specify custom options direct to esbuild by adding a targets.build.options.esbuildOptions object that take absolute precedence.

A potentially useful capability with esbuildOptions that might help depending on your project/deploy requirements is the ability to set outExtension in case you need .mjs (otherwise Nx will choose js for esm and cjs for cjs).

Troubleshooting:

  • review project.json in its entirety and ensure that you have no other configurations that will override the above (and if you do, modify them accordingly).
  • do a fresh build then review your dist/ output to make sure entrypoint files is being created for each of cjs and esm and that the generated package.json has identified the correct ones for module and main
  • make sure your consuming apps are clear about "commonjs" or "module" in their respective configs

I hope this helps you, it took some time to land on this solution. I couldn't find anything helpful in the docs.

IMHO shared libraries across front-end and back-end are a key reason to use Nx and I think it should Just WorkTM out of the box

firxworx avatar Jun 29 '23 23:06 firxworx

Obviously this is not how it should be, but setting bundle to true in the esbuild config in project.json seems to work for now.

hassannteifeh avatar Jul 01 '23 19:07 hassannteifeh

As mentioned in previous comments, the support for ESM in @nx/node is not there, however if you tell esbuild to bundle everything to one file then your compiled code is all in one entry main.js file so there are no ESM imports to worry about.

samratarmas avatar Jul 01 '23 23:07 samratarmas

@hugonteifeh and @samratarmas I agree eliminating imports entirely with bundle: true is a logical and valid solution to eliminating import problems... I have an important caveat to share regarding tree shaking.

One of the key reasons why I chose to build both cjs + esm for the shared @nx/js libraries in my project and why I suggested it above was to preserve the ability tof downstream bundlers to effectively optimize builds.

In terms of cutting down my React bundle sizes, the combination of declaring sideEffects in package.json (ideally false if you can help it) and providing ESM for downstream bundlers proved effective in my Nx project (including with the stock configuration of Vite+React).

Otherwise beware of unused/dead code from shared libraries ending up in your front-end builds. You could end up with an entire library/package's code (and potentially all of its dependencies! imagine all of @faker-js/faker or something) mashed into your bundle even if you only imported a tiny few-lines-of-code helper function from a shared utility library.

Size wasn't a huge concern for me on the back-end (though it may be for some) however on the front and it caused some big red flags with absurd bundle sizes in React apps until I addressed it.

(if anyone from nrwl/nx is reading this: please support using ESM everywhere)

firxworx avatar Jul 02 '23 03:07 firxworx

From a brand new project (node app + JS lib), if I build my depended libs then run serve it works. The other solution is to set the field bundle to true in my app project.json.

npx nx run my-js-lib:build --skip-nx-cache=true
npx nx run my-node-svc:serve

Is there a way to build all the depended libs when running serve?

chmoder avatar Aug 31 '23 13:08 chmoder

This issue has been automatically marked as stale because it hasn't had any recent activity. It will be closed in 14 days if no further activity occurs. If we missed this issue please reply to keep it active. Thanks for being a part of the Nx community! 🙏

github-actions[bot] avatar Feb 28 '24 00:02 github-actions[bot]

Is this solved now? Or is the workaround all that is available

Tiedye avatar Mar 28 '24 21:03 Tiedye

This issue has been closed for more than 30 days. If this issue is still occuring, please open a new issue with more recent context.

github-actions[bot] avatar Apr 28 '24 00:04 github-actions[bot]