11

I followed several tutorials on how to build and test an angular libary.

E.g. https://www.youtube.com/watch?v=lvjt9rBHWjo

It's working fine except that each time I'm doing a ng build mylibary, it's erasing the mylibrary folder in the dist folder. And before it has finished to build, the server (launched with npm start) detect the change (folder erased) and re compiles. And of course, since the library folder is not present anymore, there is a compilation error with no other thing to do than ctrl-c and again npm start ...

What did I missed ?

Misha Akopov
  • 12,241
  • 27
  • 68
  • 82
tweetysat
  • 2,187
  • 14
  • 38
  • 75

4 Answers4

11

Here is a super handy way to make the app reload automatically whether changes are made to the host app or to the library source code, all while keeping the original structure ready to build and publish (no need to revert any changes made to the code prior building and publishing the lib).

Having a library called my-lib, the following steps are needed:

  • Go to projects/my-lib/src/lib directory and create index.ts file which exports lib components that are meant to be publicly available

  • Edit the projects/my-lib/src/public-api.ts file in a way it exports all from the previously created index.ts file, e.g.:

    export * from './lib/index';

  • Finally, update the generated TS paths for the lib in tsconfig.json file (the root one) to point to the index.ts file created previously (instead of pointing to the dist folder)

Here is the git commit showing the changes described in the steps above.

For a more detailed info visit: Setting up live reload for Angular CLI libraries.

No need for external dependencies.

seidme
  • 12,543
  • 5
  • 36
  • 40
  • 2
    This is brilliant and should be the correct answer! I've applied this simple update to my Angular 11 workspace and working flawlessly. Library updates trigger and live reload my app fast and smoothly (live reload). So happy, this improves dev time like I was hoping. Thank you. – BBi7 Feb 07 '21 at 18:23
  • 1
    I've gone back and forth on this idea, but I'm not sure that it's necessarily safe based on comments by the Angular team. See [their discussion](https://github.com/angular/angular-cli/issues/12000), specifically this: _the really big problem is that you're not actually building the library properly. When you eventually try to build and consume the library, any number of problems can come up that you didn't see while developing._ – Mark Brodziak Mar 05 '21 at 04:37
  • @MarkBrodziak I've built 5-6 libraries this way and didn't notice any problems (wondering what they might be). The way it's described in the answer, dev-server is watching raw files for a change. Once you're ready to publish, you're going to use the standard production build process which will compile the library to the dist folder (to be published to npm from there). – seidme Mar 05 '21 at 06:50
  • 1
    The whole process is similar to the standard app development, watch raw files while developing, and build for production once ready. – seidme Mar 05 '21 at 06:59
  • Eventually, tsconfig paths could be reverted to point to the dist folder prior publishing, in order to perform some proper testing (to avoid those eventual problems that could come up?). – seidme Mar 05 '21 at 07:09
  • 1
    I've ended up settling on this approach, and it has worked just fine for our use-case, so it may have been an overabundance of caution. In our case we're not publishing our libraries as NPM modules before integration, so as far as I can see it should be wholly equivalent. – Mark Brodziak May 04 '21 at 23:46
  • When setting a path for `my-lib` in `tsconfig.json` why not just point directly to `public-api.ts` -> `projects/my-lib/src/public-api.ts`? I tried it and it works pretty well without having to do the extra steps. – DRD May 29 '21 at 19:56
  • @DRD Can't remember exactly what, but I think I had some issues when tried that (probably when it comes to the packing/publishing step) – seidme May 31 '21 at 12:01
9

You can use wait-on to await the building of the library, rimraf to clean the dist directory and npm-run-all to run the watch scripts parallel with one command from one command line window. Therefore install wait-on, rimraf and run-p as development dependency:

npm install wait-on --save-dev
npm install rimraf --save-dev
npm install run-p --save-dev

And update in package.json the scripts consequently based on the example below:

  "scripts": {
    ...
    "clean": "rimraf dist",
    "start:app": "wait-on dist/your-library-name/fesm5 && ng serve --poll 2000",
    "watch:lib": "ng build your-library-name --watch",
    "watch:all": "npm run clean && run-p watch:lib start:app",
    ...
  },

The library and the application together can be watched using npm run watch:all command.

This is how the scripts work:

"clean": "rimraf dist"

Removes the dist folder.

"start:app": "wait-on dist/your-library-name/fesm5 && ng serve --poll 2000"

Waits on the fesm5 folder in the dist directory, ng serve --poll 2000 starts the app and extends the file watch polling time to 2000 ms. In my case the last one was necessary because after a library modification the app was able to reload in the browser with the same content as previously, I could only see the new build after pressing F5.

"watch:lib": "ng build your-library-name --watch"

Builds the library in watch mode.

"watch:all": "npm run clean && run-p watch:lib start:app"

Cleans the dist folder, after that it serves the application and watches the library parallel.

Milan Tenk
  • 2,415
  • 1
  • 17
  • 24
  • 3
    I'm using Angular 10 and its solution works great! I just changed 2 things: instead of installing `npm install run-p --save-dev` I installed `npm install npm-run-all --save-dev` and instead of waiting on `dist/lib/fesm5` I waited on `dist/lib/fesm2015`. Thank you! – wldomiciano Aug 16 '20 at 21:34
  • 2
    Thanks for sharing the idea about `npm-run-all`! Yes, in case of Angular 10 waiting on `dist/lib/fesm2015` is perfect. – Milan Tenk Aug 20 '20 at 07:06
  • @MilanTenk I need to manually reload the URL once I change anything. Can't we make it auto reload URL with this? – K.Raj. Mar 26 '21 at 11:07
  • @K.Raj: I did not solve that problem yet, so I can't help you with this question. :\ – Milan Tenk Mar 26 '21 at 13:52
  • Were you successful in getting library sourceMaps to work properly with this method? – ach Sep 02 '21 at 14:48
  • I am not sure, but I think I was able to use it with sourcemaps roughly one year ago. I think I had similar configs that we have in the accepted answer here: https://stackoverflow.com/questions/51451730/angular-cli-6-build-angular-libraries-with-source-maps. – Milan Tenk Sep 08 '21 at 16:33
9

As of Angular 12, a single workspace, monolithic repository is not required but is the approach Angular takes and sometimes recommends. I've outlined and tested single and multiple workspace solutions below. The Angular team has recommended against referencing an unbuilt library many times, because the application builds are different than the libraries' [1][2].

Single Workspace

When a library and application are in the same workspace, add the watched library's output directory to the application's tsconfig paths. If the library has multiple entry points, create separate paths for the main and secondaries.

/* tsconfig.app.json */
"compilerOptions": {
  "paths": {
    "@my-scope/my-lib": ["dist/my-lib"],
    "@my-scope/my-lib/*": ["dist/my-lib/*"]
  }
}

Run the library's watch command.

ng build my-lib --configuration development --watch

Run the application's serve command.

ng serve app --configuration development

Multiple Workspaces

There are three solutions for developing a library with an application in a different workspace.

TSConfig Paths

Adding the library's output directory to the application's tsconfig paths, as demonstrated in the previous section, is the easiest solution. However, the catch is that additional paths for unwatched libraries may need to be added as well, particularly those with deep imports. These sometimes produce compile errors like the following, although, I've seen other obscure cases.

Module build failed (from .../ivy/index.js)
Error TS2339: Property 'X' does not exist on type 'typeof import("Y")
Error TS2305: Module '"X"' has no exported member 'Y'.

Always include Angular and then any that fail until successful.

/* tsconfig.app.json */
"compilerOptions": {
  "paths": {
    "@angular/*": ["node_modules/@angular/*"],
    "@my-scope/my-lib": ["../MyWorkspace/dist/my-lib"],
    "@my-scope/my-lib/*": ["../MyWorkspace/dist/my-lib/*"],
    "@third-party-scope/third-party-lib": ["node_modules/@third-party-scope/third-party-lib"]
  }
}

VSCode may require the library paths in tsconfig.json.

Directory Reference

Enable the preserveSymlinks build option in angular.json.

Install the library using the output directory path to create a symlink. npm does not install peer dependencies when installing symlinks. Install these with install-peerdeps or manually. The following is a useful npm script.

"link:my-lib": "npm install ../MyWorkspace/dist/my-lib && install-peerdeps --only-peers --silent @my-scope/my-lib",

Run the library's watch and application's serve commands.

npm Link

Detailed Explanation

Use npm link to create a symlink to the output directory.

> (cd MyWorkspace/dist/my-lib && npm link)
> (cd AppWorkspace && npm link @my-scope/my-lib)

Add tsconfig paths to tsconfig.json. The necessary paths will be those of packages installed in both workspaces and the library itself if there internal imports of entry points. The build errors are clues, but it is recommended to start with Angular. Add paths until the build succeeds. Only one wildcard can be used in each path, so multiple paths will be needed for libraries with multiple entry points and directories.

"paths": {
  "@angular/*": ["node_modules/@angular/*"],
  "@angular/common/*": ["node_modules/@angular/common/*"],
  "rxjs": ["node_modules/rxjs"],
  "@third-party-scope/third-party-lib": ["node_modules/@third-party-scope/third-party-lib"]
}

Run the library's watch and application's serve commands. Note, running npm install will replace the link.

Secondary Entry Points

Watching secondary entry points is not supported prior to Angular 14. It does work with tsconfig paths. The preferred workaround is to configure webpack to watch node_modules for the application build. Install the custom webpack builder.

npm install -D @angular-builders/custom-webpack

Configure the build architect target. Create "linked" build and serve configurations.

/* angular.json */
"projects": {
  "my-app": {
    ...
    "architect": {
      "build": {
        "builder": "@angular-builders/custom-webpack:browser",
        ...
        "configurations": {
          ...
          "linked": {
            ... a development configuration
            "customWebpackConfig": {
              "path": "./webpack.config.js"
            }
        }
      "serve": {
        "builder": "@angular-builders/custom-webpack:dev-server",
        "configurations": {
          ...
          "linked": {
            "browserTarget": "my-app:build:linked"
          }
}

Remove node_modules from the managed paths in the webpack configuration.

/* webpack.config.json */
module.exports = {
    snapshot: { managedPaths: [] }
};

Alternatively, disable the build cache.

export NG_BUILD_CACHE=0

What is a workspace?

A workspace is the root directory structure and configuration files, like angular.json and package.json, which contains and manages projects (applications and libraries). A monolithic workspace contains all projects which share a single set of dependencies. Some Angulars prefer to organize projects into separate workspaces so that they can manage dependency versions separately. For more information, check out the documentation.

Trevor Karjanis
  • 1,485
  • 14
  • 25
1

Use the following steps If you would like to include the built version of the library.

  1. Build the library in watch mode

    ng build my-lib --watch

  2. Add the path to the built library in tsconfig.json under paths

   "paths": {
      "my-lib": [
        "dist/my-lib/bundles/my-lib.umd.js"
      ]
    }
  1. Launch your main app from a new terminal window

    ng serve --open

The downside of this approach compared to the one recommended by seidme is a slightly longer build time needed to assemble the library.

To avoid opening multiple terminals you can add the following command under scripts in package.json. npm install wait-on before running the command.

"dev": "ng build my-lib --watch & (wait-on ./dist/my-lib/bundles/my-lib.umd.js --delay 2000 && ng serve --open)"

DRD
  • 5,557
  • 14
  • 14