nrwl / nx

Smart Monorepos ยท Fast CI
https://nx.dev
MIT License
23.7k stars 2.37k forks source link

How I can publish a library with all dependencies #4620

Closed dangviettuan closed 3 months ago

dangviettuan commented 3 years ago

I got an error while trying to publish my library. Here is example source code: https://github.com/dangviettuan/nx-publishable I have:

Originally posted by @dangviettuan in https://github.com/nrwl/nx/issues/3602#issuecomment-768173108

sir-captainmorgan21 commented 2 years ago

@nartc is this being considered for angular packages as well?

SStoliarchuk commented 2 years ago

+1

mattcat10 commented 1 year ago

Hi all, Nx 15 will come with an experimental feature that allows @nrwl/js to achieve this. Keep an eye out for the release blog post and give it a try. This is a tricky feature to get right so we're definitely looking for a lot of feedback.

Any update if this is available yet?

charlestupman commented 1 year ago

Think this should be an essential feature, adding a comment to keep the dream alive!

bigcakes commented 1 year ago

Hoping we can turn on that experimental feature to try it out soon, this is definitely important for us

warren-sadler commented 1 year ago

Big agree, this feels largely essential. Let's keep this train moving ๐Ÿš‚

leaderiop commented 1 year ago

did you find any solution for the this ?

arthurgubaidullin commented 1 year ago

Greetings colleagues.

I had the same task, but only for deploying cloud functions. I made a plugin for Nx. It embeds dependencies like local files.

The plugin uses itself to inject its local dependencies for publishing to npm.

While it is in alpha version and I will be glad someone will write about the bugs found.

If you have any questions, I can answer.

https://github.com/arthurgubaidullin/embed-dependencies#readme

jmroon commented 1 year ago

Ran into this as well. I expected internal libs to get bundled just as they do with apps. I'll keep the train going!

alonsovb commented 1 year ago

I'm also running into this issue.

jmroon commented 1 year ago

It turns out publishing the shared libs is not necessarily enough:

apps/
โ”œโ”€ angular-app
libs/
โ”œโ”€ lib1 - depends on shared lib, @angular/material
โ”œโ”€ lib2 - depends on shared lib, @angular/material
โ”œโ”€ shared_lib - no dependencies
โ”œโ”€ composed_lib - packages lib1, lib2, and shared together into a single module

If I run the default builder for composed_lib, lib1 and lib2 will be listed as peer dependencies. This is not what I want, as I am now forcing the consumer to install 4 libraries instead of just 1.

If I add "buildableProjectDepsInPackageJsonType": "dependencies" to composed_lib, then lib1 and lib2 get listed as dependencies, which is great, but shared lib doesn't get listed as either a dependency or peer dependency, likely because it's listed as a peer dependency of lib1 and lib2.

Now, if I add "buildableProjectDepsInPackageJsonType": "dependencies" to lib1 and lib2, shared_lib will get listed as a dependency, but @angular/material will be as well. I obviously don't want to ship an extra version of material to my consumer.

Is there any way to reconcile this?

jorgeramirezamora commented 1 year ago

It turns out publishing the shared libs is not necessarily enough:

apps/
โ”œโ”€ angular-app
libs/
โ”œโ”€ lib1 - depends on shared lib, @angular/material
โ”œโ”€ lib2 - depends on shared lib, @angular/material
โ”œโ”€ shared_lib - no dependencies
โ”œโ”€ composed_lib - packages lib1, lib2, and shared together into a single module

If I run the default builder for composed_lib, lib1 and lib2 will be listed as peer dependencies. This is not what I want, as I am now forcing the consumer to install 4 libraries instead of just 1.

If I add "buildableProjectDepsInPackageJsonType": "dependencies" to composed_lib, then lib1 and lib2 get listed as dependencies, which is great, but shared lib doesn't get listed as either a dependency or peer dependency, likely because it's listed as a peer dependency of lib1 and lib2.

Now, if I add "buildableProjectDepsInPackageJsonType": "dependencies" to lib1 and lib2, shared_lib will get listed as a dependency, but @angular/material will be as well. I obviously don't want to ship an extra version of material to my consumer.

Is there any way to reconcile this?

What if you use "peerDependency" for material?

jmroon commented 1 year ago

It turns out publishing the shared libs is not necessarily enough:

apps/
โ”œโ”€ angular-app
libs/
โ”œโ”€ lib1 - depends on shared lib, @angular/material
โ”œโ”€ lib2 - depends on shared lib, @angular/material
โ”œโ”€ shared_lib - no dependencies
โ”œโ”€ composed_lib - packages lib1, lib2, and shared together into a single module

If I run the default builder for composed_lib, lib1 and lib2 will be listed as peer dependencies. This is not what I want, as I am now forcing the consumer to install 4 libraries instead of just 1. If I add "buildableProjectDepsInPackageJsonType": "dependencies" to composed_lib, then lib1 and lib2 get listed as dependencies, which is great, but shared lib doesn't get listed as either a dependency or peer dependency, likely because it's listed as a peer dependency of lib1 and lib2. Now, if I add "buildableProjectDepsInPackageJsonType": "dependencies" to lib1 and lib2, shared_lib will get listed as a dependency, but @angular/material will be as well. I obviously don't want to ship an extra version of material to my consumer. Is there any way to reconcile this?

What if you use "peerDependency" for material?

It is actually listed as a peerDependency. buildableProjectDepsInPackageJsonType seems to pull in external peer dependencies as well. I could sed and alter the build output, I suppose, but that doesn't feel so great.

raypatterson commented 1 year ago

@nartc is it safe to assume the experimental feature referred to in https://github.com/nrwl/nx/issues/4620#issuecomment-1274027580 is https://nx.dev/packages/js/executors/swc#external ?

The "Examples" section mentions:

@nrwl/js:swc can also inline buildable libraries by setting external: 'none'

chlab commented 1 year ago

Is there any news on this? This feature would be a big help for us!

Maxhoppr commented 1 year ago

I agree, it would be a great help!

benmoses-equineregister commented 1 year ago

Bump! ๐Ÿ‘

I've also hit this issue - my scenario is ~2 new applications (building in monorepo) and an old app. I would like to build a new feature in new apps and bundle it into an importable feature to the old one. It uses some of the other libraries which do not need to be built except with the new Mono repo and the 1 buildable library.

Will make all the libraries buildable for now, but would prefer a cleaner (ideally out of box) solution.

nemonemi commented 1 year ago

@benmoses-equineregister, for such a use case, I'd propose, instead of bundling it as a package considering the Webpack module federation approach. https://webpack.js.org/concepts/module-federation/

ofirrifo commented 1 year ago

Hey, I'm using "nx": "15.7.2" how can I use it ?

emouragym commented 1 year ago

Same question, how is this possible in the current 15 version?? @nartc

pedropmedina commented 1 year ago

Using the "external": "none" option will inline all buildable libraries into the final project. Seems to be an experimental feature as of now. More on it here

An issue I'm still having is the fact the NX still includes all internal buildable libs as peerDependencies in the final package.json file. No sure this is the intended result since NX shouldn't include those buildable libs already inlined in the project.

The worst things I see about this implementation is that we would have inline each buildable library as well which causes a bit of duplication as lib-a and lib-b depend on lib-c so now we'll have lib-c in both lib-a and lib-c. Perhaps I'm doing something wrong, but I feel that handling this kind of issue to avoid such duplications is a must?

esjs commented 1 year ago

I've spent quite some time to solve this issue, and looks like for now the best option is to simply use tsc builder directly, and what I also noticed is if you add one more level in project structure - all of required packages will be placed inside target path.

image
OrShalmayev commented 1 year ago

After a week try many solutions I found a way to publish a library with all dependencies.

  1. All dependencies must be buildable projects. We can convert non-buildable projects by update workspace.json
    "build": {
         "builder": "@nrwl/web:package",
         "outputs": ["{options.outputPath}"],
         "options": {
           "outputPath": "dist/libs/<YOUR LIB IMPORT PATH>",
           "tsConfig": "libs/<YOUR PACKAGE DIRECTORY>/tsconfig.lib.json",
           "project": "libs/<YOUR PACKAGE DIRECTORY>/package.json",
           "entryFile": "libs/<YOUR PACKAGE DIRECTORY>/src/index.ts",
           "external": [<IF YOU HAVE ANY EXTERAL LIBS WANT TO IGNORE FROM BUNDLE (react, react-dom)>],
           "babelConfig": "@nrwl/react/plugins/bundle-babel", (Optional: If you lib is React Lib)
           "rollupConfig": "@nrwl/react/plugins/bundle-rollup", (Optional: If you lib is React Lib)
           "assets": [
             {
               "glob": "README.md",
               "input": ".",
               "output": "."
             }
           ]
         }
       },```
  2. Update paths in tsconfig.base.json file same with . Example: YOUR LIB IMPORT PATH = @myorg/my-awesome-lib
    "paths": {
     "@myorg/my-awesome-lib": ["libs/ui/awsome-button/src/index.ts"],
     .....
    },
  3. Build your library with --with-deps flag.
  4. Rebuild you library with custom rollup config

    'use strict';
    Object.defineProperty(exports, '__esModule', { value: true });
    const builtins = require('rollup-plugin-node-builtins');
    const resolve = require('@rollup/plugin-node-resolve');
    const fileExtensions = ['.js', '.jsx', '.ts', '.tsx'];
    
    function getRollupOptions(options) {
     const extraGlobals = {
       // <YOUR EXTRAGLOBALS NAMES> 
       // Example:
       'react-dom': 'ReactDOM',
       'styled-components': 'styled',
       '@emotion/core': 'emotionCore',
     };
     if (Array.isArray(options.output)) {
       options.output.forEach((o) => {
         o.globals = Object.assign(Object.assign({}, o.globals), extraGlobals);
       });
     } else {
       options.output.globals = Object.assign(
         Object.assign({}, options.output.globals),
         extraGlobals
       );
     }
     options.plugins.push(
       resolve.nodeResolve({
         preferBuiltins: true,
         extensions: fileExtensions,
         moduleDirectories: ['dist/libs', 'node_modules'], // IMPORTANT
       })
     );
     options.plugins.push(builtins());
     options.external = [
       // YOUR EXTERNAL LIBS (Libs you don't want to bundle)
       // Example:
       'react',
       'react-is'
     ];
     return options;
    }
    module.exports = getRollupOptions;
  5. NX will automatically add dependencies and buildable libraries to dependencies or peerDependencies. You can select by add flag buildableProjectDepsInPackageJsonType= dependencies or peerDependencies to buid command.
  6. Go to the library and manually remove your buildable libraries from package.json because after publishing to NPM you can not download them.
  7. Publish your Package to NPM PS: You should test your package before publishing to NPM to make sure it works properly. I faced 3 problems.
  • CSS not extract. To fix it just add the flag --no-extractCss to the build command. I don't know why my buildable library exported to 2 CSS files but when bundle my publishable library CSS files not included. So embed CSS inside JS is my choice.
  • NX missing some dependencies in package.json. After build my publish library I found that some dependencies is missing and I have to manually add them to package.json before publish to NPM.
  • Typescript module augmentation: In my project, I use module augmentation to override a third-party interface. After the bundle, an error occurred because of undefined property. To fix it I remove third-party library from the external list to bundle it with my library.

@dangviettuan Thank you, can you please share a repo with the workaround solution? I tried to implement your solution but it didn't worked.

wall-street-dev commented 1 year ago

Don't know if this will solve all of the requirements exposed in this page, but I have created a custom executor to address my specific use case (described here). Hopefully it could help you too!

Here's the link to the npm package: https://www.npmjs.com/package/@altack/nx-bundlefy And the source code: https://github.com/altack/nx-bundlefy

StringKe commented 1 year ago

If anyone is using pnpm and is experiencing dependencies not being written correctly to package.json please replace them with yarn or npm !!!

Most of the dependency lookup requires the createProjectGraphAsync provided by nx, which doesn't seem to work correctly in pnpm at the moment.

bommox commented 1 year ago

Don't know if this will solve all of the requirements exposed in this page, but I have created a custom executor to address my specific use case (described here). Hopefully it could help you too!

Here's the link to the npm package: https://www.npmjs.com/package/@altack/nx-bundlefy And the source code: https://github.com/altack/nx-bundlefy

Thanks for your package.

I have been reading your code and have a couple of questions:

wall-street-dev commented 1 year ago
  • Do I need to explicitly write the bundledDependencies entry in the package.json?
  • All my buildable packages are automatically scoped by NX. Is this a problem?

Hi @bommox,

Hope it helps!

github-actions[bot] commented 1 year ago

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! ๐Ÿ™

danielsharvey commented 1 year ago

Keep active please

clemenscodes commented 1 year ago

Bumped into this as well and specifying "external": "none" in the builder of the project that I want to publish has solved the issue for me. The shared libraries that are used in the published project get correctly bundled without having to publish the libs or specify the libs in the package.json file of the published project.

ajwootto commented 1 year ago

External does appear to work but if you want to bundle only some of your workspace libs while maintaining others as independently published, it requires you to list all those "external" libs manually. I would have expected there to be a setting like "external: 'buildable'" that just automatically externalizes libs that are buildable

meeroslav commented 1 year ago

This should be automated based on your lib's package.json:

cc @jaysoo

ajwootto commented 1 year ago

How does that interact with the externals setting then? Do you just not set it in that case? Or do you have to list the same packages ?

ajwootto commented 1 year ago

The "external" setting does seem to successfully produce a working output with certain packages inlined, but it has a bunch of bugs when interacting with other parts of Nx. I filed a few yesterday:

https://github.com/nrwl/nx/issues/20019 https://github.com/nrwl/nx/issues/20016 https://github.com/nrwl/nx/issues/20015 https://github.com/nrwl/nx/issues/20014 https://github.com/nrwl/nx/issues/20033

github-actions[bot] commented 11 months ago

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! ๐Ÿ™

ajwootto commented 11 months ago

not stale

dearlordylord commented 10 months ago

would be very nice to have this for latest NX version!

dearlordylord commented 10 months ago

In @nx/js:tsc executor that is used in build step for ts projects, adding "external": "none" to options as per https://nx.dev/nx-api/js/executors/tsc seems to almost solve the issue, but not entirely. It still includes aliased imports even when all my libs are "external": "none"

meeroslav commented 10 months ago

Assigning @jaysoo since he was working on this functionality

dtap001 commented 9 months ago

any update?

github-actions[bot] commented 7 months ago

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! ๐Ÿ™

dearlordylord commented 7 months ago

please unstale

FrozenPandaz commented 7 months ago

This has been addressed with our new Nx release command: https://nx.dev/features/manage-releases

Please check that out and let us know what you think!

ajwootto commented 7 months ago

@FrozenPandaz can you provide an example of how the release command addresses the concern here? It doesn't seem to have anything to do with bundling libraries inside of other libraries

dearlordylord commented 7 months ago

The last time I was told to use the nx release command, it was doing something entirely different. It's either bugged out or has completely separate scope since, at the least, January 8th: https://discord.com/channels/1143497901675401286/1193795596235063356/1193795596235063356

FrozenPandaz commented 7 months ago

Ah, sorry. I've reopened the issue. :+1:

billdinger commented 6 months ago

Thanks to everyone who has commented. As someone who just spent ~13 hours going slowly mad thinking he had forgotten or was crazy or didn't survive his last plane trip because this scenario just wasn't working...

This sure would be nice to have! It's not obvious/spelled out at all that you can't have a normal nx library that is imported by other published libraries. And attempting to do so causes the 'rootDir' is expected to contain all source files. errors which don't make a lot of sense.

I can confirm that external: "none" works although if you want to limit the scope of what is inlined in your package to just the specific shared library you are using in your project.json

  "targets": {
    "build": {
      "executor": "@nx/js:tsc",
      "outputs": ["{options.outputPath}"],
      "options": {
        "external": ["@my-project/lib/shared"],

where "@my-project/lib/shared" is whatever shared library or libraries you need. That will inline them into the package and cause the error to go away. Unfortuantely this feature is marked deprecated and scheduled for removal in nx 20 which means this solution, while it works, means you can never upgrade your project. Not great!

Anway +1 and thanks to all the folks who have commented here and helped.

indra4you commented 6 months ago

First most, I am not that experienced with monorepo & nx. I am getting similar error and best to my knowledge I tried all possible options shared on this thread as well as whatever answer I got on google, but none of them worked for me. May I am doing it completely wrong or my expectation itself is wrong!

What I am expecting is, during the build

I am yet to figure out on how to change symbolic link of first-package in second-package package.json file with exact version during publishing.

Here is my repo and attached screenshot of the error I am getting.

image

sonnd08 commented 5 months ago

Also, seem like external: "none" only bundle packages inside nx workspace, it does not bundle outside package like dayjs, moment, ... I would like to create a package that not dependent to any other packages instead.

rolandas-valantinas commented 4 months ago

There is very basic example where this is required for us and I'm surprised that no one mentioned it. We have about 10 logically distinct libraries which we use to create very thin lambdas. Whole NX affected approach is very beneficial as only some lambdas are packaged and deployed when changes in libraries occur - no problem at this level.

However, we also have utility methods across those libraries which we want to share with other teams. One could say just pull all of the utility methods into single library, but then affected becomes way less useful - affected doesn't operate at method level, so updating single utility method will cause all lambdas to rebuild though only couple are using updated method.

external: "none" sort of works, however it is not compatible with @nx/dependency-checks eslint plugin which is shouting about missing local libraries, even though everything works fine both locally and in published package. The only way to stop this is to list all of libraries used within in ignoredDependencies in .eslintrc.json and it doesn't seem like there is way to use regex or specify just scope.

And like someone else said external: "none" is getting removed in nx 20 ๐Ÿคทโ€โ™‚๏ธ