6

I have two libraries: core and client.

Core is meant to be private and client is the one to be published. I want to include core inside client bundle (client uses core functions), so the final user does not need to manage core dependency.

¿How can I do this? Any help would be appreciated.

packages

packages
├── client
│   ├── ng-package.json
│   ├── node_modules
│   │   └── @lib
│   │       └── core -> ../../../core
│   ├── package-lock.json
│   ├── package.json
│   └── src
│       ├── lib
│       │   ├── client.ts
│       └── public-api.ts
└── core
    ├── ng-package.json
    ├── package-lock.json
    ├── package.json
    └── src
        ├── lib
        │   ├── core.ts
        └── public-api.ts

As you see, there is a symbolic in from packages/client/node_modules/@lib-core pointing to core. This makes that when I run the app in local environment it finds the reference to core. The problem is that after generating the build, there is no link.

core/package.json

{
  "dependencies": { "tslib": "^1.10.0" },
  "main": "src/public-api.ts",
  "name": "@lib/core",
  "scripts": {
    "build": "ng build core",
    "test": "ng test core"
  },
  "version": "0.0.1"
}

core/ng-package.json

{
  "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
  "dest": "../../dist/core",
  "lib": {
    "entryFile": "src/public-api.ts"
  }
}

client/package.json

{
  "bundledDependencies": ["@lib/core"],
  "dependencies": {
    "@lib/core": "^0.0.1",
    "tslib": "^1.10.0"
  },
  "main": "src/public-api.ts",
  "name": "@lib/client",
  "scripts": {
    "build": "ng build client",
    "test": "ng test client"
  },
  "version": "0.0.1"
}

client/ng-package.json

{
  "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
  "dest": "../../dist/client",
  "lib": {
    "entryFile": "src/public-api.ts",
    "umdModuleIds": {
      "@lib/core": "@lib/core"
    }
  },
  "whitelistedNonPeerDependencies": ["@lib/core"]
}

As you can see, I use npm bundledDependencies in client/package.json, as mentioned here. This works when running npm pack inside package/client, but I want to build with Angular first (so it generates javascript code and apply performance techniques). My intention is to pack after generating the build.

dist

After generating the bundle, I tried running npm install inside dist/client to see if the dependency could be installed and packed from there.

It throws the error 404 Not Found '@lib/core@^0.0.1' is not in the npm registry.

This is the tree of dist/client after build:

client
├── README.md
├── bundles
│   ├── lib-client.umd.js
│   ├── lib-client.umd.js.map
│   ├── lib-client.umd.min.js
│   └── lib-client.umd.min.js.map
├── esm2015
│   └── ...
├── esm5
│   └── ...
├── fesm2015
│   └── ...
├── fesm5
│   └── ...
├── lib
│   └── client.d.ts
├── lib-client.d.ts
├── package.json
└── public-api.d.ts

The dist/client/package.json has the same dependencies defined inside packages

  "bundledDependencies": [
    "@lib/core"
  ],
  "dependencies": {
    "@lib/core": "^0.0.1",
    "tslib": "^1.10.0"
  },

This are the imports in the bundles/lib-client.umd.js file:

(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('tslib'), require('@lib/core')) :
    typeof define === 'function' && define.amd ? define('@lib/client', ['exports', 'tslib', '@lib/core'], factory) :
    (global = global || self, factory((global['lib'] = global['lib'] || {}, global['lib']['client'] = {}), global.tslib, global['@lib/core']));
}(this, (function (exports, tslib, core) { 'use strict';

It looks that, instead of copying core code its referencing it as an external dependency.

Recap

The objective of this question is to generate a single bundle with the library to the user.


I also have another question on how to test this in ci: Lerna package import in CI

adrisons
  • 3,443
  • 3
  • 32
  • 48

2 Answers2

4

You can try with the following configurations.

In tsconfig.ts search for dist package:

"paths": {
      "@angular/*": ["./node_modules/@angular/*"],
      "@lib/core": [
        "../../../core/dist/@lib/core" 
      ]
    }

Still in tsconfig.ts (configurations to no have errors):

"angularCompilerOptions": {
  "fullTemplateTypeCheck": true,
  "strictInjectionParameters": true,
  "enableIvy": true,
  "importHelpers": true
}

In angular.json search for preserveSymlinks configuration:

projects.<project-name>.architect.build.options.preserveSymlinks: true

Added steps: Remove core library dependency from your client package.json. You not need it:

  "bundledDependencies": [
    ...
  ],
  "dependencies": {
    ...
  },

You can import every function of core library simply with:

import {...} from '@lib/core';

and they will bundled together when you'll run ng build on client library.

This setup worked to me, for your use case.

Sandman
  • 1,480
  • 7
  • 23
  • Thanks for your answer. For you, did this bundle the two libraries together? Because the app runs ok for me, the objective of the question is to generate the single bundle. After these changes, when running `npm install` in `dist/client` I see the same error (trying to fetch from npm registry) – adrisons Jan 21 '21 at 12:22
  • I modified my response. Remove core library dependency from your client package.json. You not need it. You can import every function of core library simply with: import {...} from '@lib/core'; and they will bundled together when you'll run `ng build` on client library. – Sandman Jan 21 '21 at 12:32
  • Please tell me how it is generated for you, the final bundle. I still see the `require('@lib/core')` in `bundles/lib-client.umd.js`, instead of all together. I need this to work in production with only client lib. – adrisons Jan 21 '21 at 13:15
  • Bundling them together do you have error calling that library in every other project? Have you removed all the dependencies? – Sandman Jan 21 '21 at 13:20
  • Yes, removed all dependencies. Then, inside a simple vanilla javascript test I import the generated umd.js script. Client class is there, but when it refers to core, it throws an error. – adrisons Jan 21 '21 at 15:33
0

Since your core is private and client has a direct dependency, you don't need to treat it as a local package. Instead directly access the core module using relative path.

i.e) remove the dependency from package.json and import the core dirctly using relative path in your client package.

eg:

Step 1: remove core dependency from client package.json

client/package.json

{
  "bundledDependencies": ["@lib/core"],
  "dependencies": {
    "tslib": "^1.10.0"
  },
  "main": "src/public-api.ts",
  "name": "@lib/client",
  "scripts": {
    "build": "ng build client",
    "test": "ng test client"
  },
  "version": "0.0.1"
}

Step 2: import core using relative path or alias name (using @)

Import core in your client.ts (inside client package inside src/lib)

packages/client/src/lib/client.ts

import core from "@lib/core"

vineeth pappu
  • 524
  • 2
  • 7
  • Thanks for the answer but same error in the test: Cannot find module '@lib/core' from 'src/lib/client.spec.ts'. Also, in the client umd bundle there is not core's code. I am using tsconfig paths so the test knows where to look (dist/client) – adrisons Jan 21 '21 at 11:52