Storybook: @storybook/angular WebPackConfigOptions is relying on hardcoded `build` target

Created on 11 Jun 2020  ยท  16Comments  ยท  Source: storybookjs/storybook

Describe the bug

Currently, the @storybook/angular plugin is relying hardcoded on the build target from the Angular Architect. This is a problem as it wants to collect the tsConfig information from the build target.

https://github.com/storybookjs/storybook/blob/next/app/angular/src/server/angular-cli_config.ts#L100

// https://github.com/storybookjs/storybook/blob/next/app/angular/src/server/angular-cli_config.ts#L100
 const { options: projectOptions } = project.architect.build;
  const normalizedAssets = normalizeAssetPatterns(
    projectOptions.assets,
    dirToSearch,
    project.sourceRoot
  );
  const projectRoot = path.resolve(dirToSearch, project.root);
  const tsConfigPath = path.resolve(dirToSearch, projectOptions.tsConfig) as Path;

You can not rely on the project options of the hardcoded build target to get the tsConfig as this will result in not finding a tsConfig if you have a build target that has not a tsConfig field.

As you can have a different builder for the architect build step for example:

      "architect": {
        "build": {
          "builder": "@nrwl/workspace:run-commands",
          "options": {
            "parallel": true,
            "commands": [
              {
                "command": "npx ng run my-app:build-frontend"
              },
              {
                "command": "npx ng run my-app:build-server"
              }
            ]
          },

I want to specify storybook which target it should take in this case myapp:build-frontend to get the tsConfig options out of it. Otherwise, it will result in the following Error:

[error] TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received undefined
    at validateString (internal/validators.js:117:11)
    at Object.resolve (path.js:980:7)
    at Object.getAngularCliWebpackConfigOptions (/.../node_modules/@storybook/angular/dist/server/angular-cli_config.js:99:39)
    at Object.webpackFinal (/.../node_modules/@storybook/angular/dist/server/framework-preset-angular-cli.js:7:56)
    at /.../node_modules/@storybook/core/dist/server/presets.js:261:72
angular feature request has workaround inactive

Most helpful comment

So If you want to implement this approach I could help you with that, as it is my daily job, creating angular builders and setup for https://github.com/Dynatrace/barista ๐Ÿ˜‚

All 16 comments

For all who are running into the same issue it is a workaround to add into the first build step a tsConfig option.

Furthermore, the detection of the project inside the architect does not work really well.
As it is not really compatible with monorepo structures.

// https://github.com/storybookjs/storybook/blob/next/app/angular/src/server/angular-cli_config.ts#L74
let projectName;
  const firstProjectName = Object.keys(projects)[0];
  if (projects.storybook) {
    projectName = 'storybook';
  } else if (defaultProject && projects[defaultProject]) {
    projectName = defaultProject;
  } else if (projects[firstProjectName]) {
    projectName = firstProjectName;
  }

My Proposal to this solution would be providing for the angular package an angular builder that has all the information about the angular cli through the architect.

So If you like this idea I'm willing to contribute this โ€“> Could be a ng add @storybook/angular schematic that sets up everything properly and enables having multiple storybook instances for a monorepo structure on app or lib level.

Hi @lukasholzer,

Storybook has a CLI but since it is framework agnostic it might be odd to offer a start-storybook --angular-project=project1 ๐Ÿค”

If I understand you correctly then you propose that you can do start multiple Storybook projects inside your Angular mono-repo using ng serve storybook-project1, ng serve storybook-project2 etc.?

Yea angular offers the opportunity to have multiple apps and libraries - https://angular.io/guide/file-structure#multiple-projects. Or https://nx.dev/angular that is specialized to provide a monorepo outlet even with storybook support. I think developers who are using storybook are working in big environments with a lot of different domains and monorepos.

Sample structure:

apps/
 - app1
 - app2
 - app3
libs/
  - lib1
  - lib...52

So what you basically have is you can have multiple domains in a workspace this can lead to having a lot of different UIs and design systems that you want to call individually with storybook. Basically every lib or app can have a storybook integration.

In an angular world, you never use a different CLI as the angular CLI so what Nx is offering it provides the storybook layer for the angular CLI https://nx.dev/angular/plugins/storybook/builders/build.

The downside is that they only wrap the core standalone function https://github.com/nrwl/nx/blob/master/packages/storybook/src/builders/build-storybook/build-storybook.impl.ts#L71 to run storybook. So they have to rely on gathering the options by storybook if it would be configurable you could specify those things via the angular builder.

import * as build from '@storybook/core/standalone';

Those builders for the Angular CLI are basically pure functions that getting called with the options that are provided via a JSON schema. Then the function has to return {success: true} or false if it fails. Inside there you can call everything what you want.

So using a platform independent way for the core is perfectly fine. But when it comes to I decide to install and use angular I would go with their conventions.

ng run my-lib:serve-storybook or ng run my-otherlib:build-storybook would be the way in an angular universe to call those builders.

I too think the Angular implementation of Storybook could benefit from this a lot. And I agree: Angular devs are used to use the Angular CLI for everything. Nrwl/Nx already implemented a couple of Storybook schematics just for Nx.

@ndelangen @shilman what do you think about this?

In general, all runnable applications "register" themself in the angular.json. By all I mean

  • e2e
  • jest / karma
  • Angular applications
  • In Nrwl/Nx

    • NestJS server applications

    • node.js applications

    • react applications

There's no limit. The Angular Architect is framework agnostic. The Angular team wrote their own builders for the Architect to run Angular applications for example.

Some thoughts

Storybook setup

By default,

npx @storybook/cli init

should add a project to the angular.json. We sort of having this feature already. You can add this to your angular.json for example:

projects: {
  "storybook": {
    "styles": ["css-file.css", "scss-file.scss"]
    "scripts": ["external-script.js"]
  }
}

See https://github.com/storybookjs/storybook/blob/next/app/angular/src/server/angular-cli_config.ts#L75

The downside of the current implementation in a mono-repo environment is that it only supports one single configuration for all applications.

As a comfort utility (aka what Angular devs normally do when adding new things) we can add a schematic to @storybook/angular that allows us to run this in an angular environment. (Optional, not related to the original issue but got mentioned here https://github.com/storybookjs/storybook/issues/11125#issuecomment-642527158)

// Would do the same as npx @storybook/cli init
ng add @storybook/angular

Storybook/angular mono-repo support

Add a builder that allows to have multiple storybooks inside one angular mono-repo

Here's how Angular project entries normally look like (stripped out everything that is not important)

"projects": {
    "my-application": {
      "projectType": "application",
      "root": "projects/my-application",
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "outputPath": "dist/my-application",
            "tsConfig": "tsconfig.app.json",
            "assets": [
              "src/favicon.ico",
              "src/assets"
            ],
            "styles": [
              "src/styles.css"
            ],
            "scripts": []
          }
        },
        "serve": {
          "builder": "@angular-devkit/build-angular:dev-server",
          "options": {
            "browserTarget": "my-application:build"
          },
          "configurations": {
            "production": {
              "browserTarget": "my-application:build:production"
            }
          }
        },
        "test": {
          "builder": "@angular-devkit/build-angular:karma",
          "options": {
            ...
          }
        }
      }
    }
}

This is how a Storybook project entry could look like

"my-application:storybook": {
  "projectType": "application",
  "root": "projects/my-application",
  "architect": {
    "build": {
      "builder": "@storybook/angular:browser",
      "options": {
        "outputPath": "dist/my-application-storybook",
        "tsConfig": ".storybook/tsconfig.json",
        "assets": [
          "src/assets"
        ],
        "styles": [
          "src/styles.css"
        ],
        "scripts": []
      }
    },
    "serve": {
      "builder": "@storybook/angular:serve",
    }
  }
}

This configuration + some additions to the current @storybook/angular implementation allows an Angular mono-repo to run commands like

// this will run the angular project projects.my-application:storybook
// the builder is the important part in the configuration as this is the one running start-storybook for this particular directory 
ng serve my-application:storybook
ng build my-application:storybook

TL;DR not possible, sorry ๐Ÿ™ƒ

@lukasholzer we are still on the same page, are we?

Yea we are on the same page โ€“ exactly what I meant only your sketch of the angular.json is not completely correct. You cannot use : for target names. But this is a small detail.

{
  "my-application": {
    "projectType": "application",
    "root": "projects/my-application",
    "architect": {
      "build": {
        "builder": "@angular-devkit/build-angular:browser",
        "options": {
          ...
        }
      },
      "serve": {
        "builder": "@angular-devkit/build-angular:dev-server",
        "options": {
          "browserTarget": "my-application:build"
        }
      },
      "storybook-build": {
        "builder": "@storybook/angular:browser",
        "options": {
          "outputPath": "dist/my-application-storybook",
          "tsConfig": ".storybook/tsconfig.json",
          "assets": [
            "src/assets"
          ],
          "styles": [
            "src/styles.css"
          ],
          "scripts": []
        }
      },
      "storybook-serve": {
        "builder": "@storybook/angular:serve",
        "options": {
          "buildTarget": "my-application:storybook-build"
        }
      }
    }
  }
}

You would run it then with ng run my-application:build-storybook or ng run my-application:serve-storybook.

So If you want to implement this approach I could help you with that, as it is my daily job, creating angular builders and setup for https://github.com/Dynatrace/barista ๐Ÿ˜‚

@kroeder @lukasholzer this sounds reasonable to me!

@lukasholzer do you have time to make the first step and create a PR where we can continue the discussion? I can help you set things up in the repo ๐Ÿ™‚ you can join the storybook discord server or DM me on twitter if you want

@kroeder for sure would be a pleasure to contribute this โ€“ Maybe I can spare some time on the weekend or next week to tackle this! Do you have a link to the discord channel?

Can you assign me the issue?

@lukasholzer sounds great!

Let's chat about the kick-off in Discord
https://discord.gg/E4CGPQ -> KaiR#5732

Hi everyone! Seems like there hasn't been much going on in this issue lately. If there are still questions, comments, or bugs, please feel free to continue the discussion. Unfortunately, we don't have time to get to every issue. We are always open to contributions so please send us a pull request if you would like to help. Inactive issues will be closed after 30 days. Thanks!

@kroeder sorry hadn't had the time yet. Will try next week to squeeze some time into :D

+1 This would really help my use case.

I'm trying to build an angular library and the builder for that @angular-devkit/build-ng-packagr:build doesn't support assets like the normal app builder does. This means I need to have a build configuration for running storybook and a separate one for actually bundling my library.

As a workaround I have some npm scripts that I'm using to modify the angular.json before running my command. Not very clean ๐Ÿ˜ข

Hi everyone! Seems like there hasn't been much going on in this issue lately. If there are still questions, comments, or bugs, please feel free to continue the discussion. Unfortunately, we don't have time to get to every issue. We are always open to contributions so please send us a pull request if you would like to help. Inactive issues will be closed after 30 days. Thanks!

Hey there, it's me again! I am going close this issue to help our maintainers focus on the current development roadmap instead. If the issue mentioned is still a concern, please open a new ticket and mention this old one. Cheers and thanks for using Storybook!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

miljan-aleksic picture miljan-aleksic  ยท  3Comments

shilman picture shilman  ยท  3Comments

wahengchang picture wahengchang  ยท  3Comments

arunoda picture arunoda  ยท  3Comments

MrOrz picture MrOrz  ยท  3Comments