teambit / bit-angular

Angular support for bit harmony
28 stars 3 forks source link

Angular Material + custom env dependency errors #47

Closed gmichael27 closed 2 years ago

gmichael27 commented 2 years ago

Background

Hey all - our goal has been to set up an Angular components library using Bit. We've been having issues getting anything up-and-running, most likely because our existing components we want to pull in are built completely in Angular Material's design system and we're having trouble getting it to play nice.

The installation guide specifies running ng add material in an existing Angular project, which runs a script to pull in dependencies and configure everything for proper styling etc. Since we're outside of a proper Angular project, we looked at the source of this schematic to see what it does. In short terms it determines your current @angular/core version and installs matching versions of the the following packages:

  1. @angular/material
  2. @angular/cdk
  3. @angular/forms
  4. @angular/animations

It then runs setup-project.ts which in a nutshell:

  1. Imports/installs BrowserAnimationsModule into root module from @angular/platform-browser/animations if animations are to be used (we prefer 'yes')
  2. Performs various tasks to add custom CSS, linking it to index.html, etc. (We haven't gotten into the details since we haven't gotten this far yet.)

In lieu of running ng add, our task has been to reverse-engineer this and pull it all in ourselves. Our procedure is in the section below. We've followed a couple existing issues as a guide and opened our own when a dependency issue was found (and addressed):

Currently, we're still running into errors that seem to be build-chain related but are having trouble determining if this is misconfiguration or a bug in Bit, as we don't encounter it until pulling in Angular Material.

Procedure

Set up Bit workspace

Create a new Bit/Angular workspace, then create a module and custom env:

bit new ng-workspace bit-angular-material -a teambit.angular/angular
cd bit-angular-material
bit create ng-module ui/material-button
bit create ng-env angular-material-env

Amend workspace.jsonc as shown in #35 so that components now use the angular-material-env:

/**
 * this is the main configuration file of your bit workspace.
 * for full documentation, please see: https://bit.dev/docs/workspace/workspace-configuration
 **/ {
  "$schema": "https://static.bit.dev/teambit/schemas/schema.json",
  /**
   * main configuration of the Bit workspace.
   **/
  "teambit.workspace/workspace": {
    /**
     * the name of the component workspace. used for development purposes.
     **/
    "name": "bit-angular-material",
    /**
     * set the icon to be shown on the Bit server.
     **/
    "icon": "https://static.bit.dev/bit-logo.svg",
    /**
     * default directory to place a component during `bit import` and `bit create`.
     * the following placeholders are available:
     * name - component name includes namespace, e.g. 'ui/button'.
     * scopeId - full scope-id includes the owner, e.g. 'teambit.compilation'.
     * scope - scope name only, e.g. 'compilation'.
     * owner - owner name in bit.dev, e.g. 'teambit'.
     **/
    "defaultDirectory": "{scope}/{name}",
    /**
     * default scope for all components in workspace.
     **/
    "defaultScope": "company.scope"
  },
  /**
   * main configuration for component dependency resolution.
   **/
  "teambit.dependencies/dependency-resolver": {
    /**
     * choose the package manager for Bit to use. you can choose between 'yarn', 'pnpm'
     */
    "packageManager": "teambit.dependencies/yarn",
    "policy": {
      "dependencies": {
        "@teambit/angular": "1.1.7"
      },
      "peerDependencies": {}
    }
  },
  /**
   * workspace variants allow to set different subsets of configuration for components in your
   * workspace. this is extremely useful for upgrading, aligning and building components with a new
   * set of dependencies. a rule can be a directory or a component-id/namespace, in which case,
   * wrap the rule with curly brackets (e.g. `"{ui/*}": {}`)
   * see https://bit.dev/docs/workspace/variants for more info.
   **/
  "teambit.workspace/variants": {
    "angular-material-env": {
      "teambit.harmony/aspect": {}
    },
    "*": {
      "scope/angular-material-env": {}
    }
  },
  "teambit.angular/angular@1.1.7": {},
  "teambit.generator/generator": {
    "aspects": [
      "scope/angular-material-env"
    ]
  }
}

Pull dependencies, run the server, and navigate to localhost:3000 to verify there are no issues:

bit install
bit start

Everything up to this point works as expected. All pages can be visited without problems.

Pull in Angular Material

First we determine the version of Angular uses under-the-hood by peeking at node_modules/@angular/core/package.json, where we see "version": "13.2.7". Unfortunately Material does not have this exact version, so instead we fall to 13.2.6 for everything.

To emulate the dependency portion of the ng add schematic, we run:

bit install @angular/material@13.2.6 --type peer
bit install @angular/animations@13.2.6 --type peer
bit install @angular/cdk@13.2.6 --type peer
bit install @angular/forms@13.2.6 --type peer
bit install @angular/platform-browser/animations@13.2.6 --type peer

We assume that all other peer dependencies are met by parent aspect's dependencies (i.e. @angular/core), so we stop here. Since we now have access to Material modules, we change our scope/ui/material-button/material-button.module.ts component's contents to use some of them:

import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatDialogModule } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu';
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatSelectModule } from '@angular/material/select';
import { MatTableModule } from '@angular/material/table';
import { MaterialButtonComponent } from './material-button.component';

@NgModule({
  declarations: [
    MaterialButtonComponent
  ],
  imports: [
    FormsModule,
    MatPaginatorModule,
    MatTableModule,
    MatFormFieldModule,
    MatIconModule,
    MatButtonModule,
    MatMenuModule,
    MatProgressBarModule,
    MatSelectModule,
    MatDialogModule
  ],
  exports: [
    MaterialButtonComponent
  ]
})
export class MaterialButtonModule { }

We run the app again with bit start, navigate to the webapp, and encounter this error on viewing material-button:

Error: The injectable 'PlatformLocation' needs to be compiled using the JIT compiler, but '@angular/compiler' is not available.

The injectable is part of a library that has been partially compiled.
However, the Angular Linker has not processed the library such that JIT compilation is used as fallback.

Ideally, the library is processed using the Angular Linker to become fully AOT compiled.
Alternatively, the JIT compiler should be loaded by bootstrapping using '@angular/platform-browser-dynamic' or '@angular/platform-server',
or manually provide the compiler with 'import "@angular/compiler";' before bootstrapping.

▶ 4 stack frames were collapsed.

__webpack_require__
http://localhost:3000/preview/pillar.material/env/angular-material-env/static/js/main.bundle.js:609318:33
  609315 | /******/             var execOptions = { id: moduleId, module: module, factory: __webpack_modules__[moduleId], require: __webpack_require__ };
  609316 | /******/             __webpack_require__.i.forEach(function(handler) { handler(execOptions); });
  609317 | /******/             module = execOptions.module;
> 609318 | /******/             execOptions.factory.call(module.exports, module, module.exports, execOptions.require);
         |                                        ^  609319 | /******/      } catch(e) {
  609320 | /******/             module.error = e;
  609321 | /******/             throw e;

We have tried some other steps as well:

All of these steps result in the same problem above.

This is the latest error we've gotten stuck on in the last week of trying to get Angular Material to work with Bit. We're really hoping for some guidance on this. Material seems like a popular design system for Angular components and we would love to have a working example as an entry point and to ensure Bit can support it.

Any advice is greatly appreciated in getting this working.

Versions used:

ocombe commented 2 years ago

Hello, thank you for your report and the detailed breakdown. I was able to reproduce the issue and I think that I know what's causing it. I will be working on a fix, but for now you can just fix it for you if you go to your custom env and remove this line:

const overrideAngularEnvOptions = angular.overrideAngularEnvOptions({ useAngularElementsPreview: true });

Then run bit compile and after that bit start should work.

ocombe commented 2 years ago

Fixed in v1.1.8 of the angular env (if you update you don't need to change the custom env, it's up to you if you want to use angular elements for the preview or not, it's faster and the preview is better, but it's new and can be subject to bugs).

jkeczan commented 2 years ago

might be one of the best breakdowns of an issue I have ever seen....thanks @gmichael27

gmichael27 commented 2 years ago

Thanks @ocombe - bit update updated our dependencies version of @teambit/angular to 1.1.8 and did fix the dependency errors on startup. I did want to point out/ask if that should be expected to also update the version of the "teambit.angular/angular@1.1.8": {} (env?) line; ours remained at 1.1.7 after update so we incremented manually.

We do continue to encounter problems using Angular Material and I'd like to use this issue to get to the bottom of everything. Let me know if it's best to make a new issue; we're just building off the setup described above.

We create a new ng-module component and confirm its environment:

bit create ng-module ui/components/material-selector
bit env

Output:

┌─────────────────────────────────────────────────┬──────────────────────────────────────────┐
│ component                                       │ env                                      │
├─────────────────────────────────────────────────┼──────────────────────────────────────────┤
│ pillar.material/env/angular-material-env        │ teambit.harmony/node                     │
├─────────────────────────────────────────────────┼──────────────────────────────────────────┤
│ pillar.material/ui/components/material-selector │ pillar.material/env/angular-material-env │
└─────────────────────────────────────────────────┴──────────────────────────────────────────┘

We import some Material modules into material-selector.module.ts:

import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatMenuModule } from '@angular/material/menu';
import { MatSelectModule } from '@angular/material/select';
import { BrowserModule } from '@angular/platform-browser';
import { MaterialSelectorComponent } from './material-selector.component';

@NgModule({
  declarations: [
    MaterialSelectorComponent
  ],
  imports: [
    BrowserModule,
    CommonModule,
    FormsModule,
    MatFormFieldModule,
    MatMenuModule,
    MatSelectModule
  ],
  exports: [
    MaterialSelectorComponent
  ]
})
export class MaterialSelectorModule { }

We then modify the template in material-selector.component.ts to use a simple example from the Material examples:

import { Component } from '@angular/core';

@Component({
  selector: 'material-selector',
  template: `
    <p>
    <mat-form-field appearance="fill">
      <mat-label>Choose an option</mat-label>
      <mat-select>
        <mat-option value="option1">Option 1</mat-option>
        <mat-option value="option2" disabled>Option 2 (disabled)</mat-option>
        <mat-option value="option3">Option 3</mat-option>
      </mat-select>
    </mat-form-field>
    </p>
`,
  styleUrls: ['./material-selector.component.scss']
})
export class MaterialSelectorComponent {
  constructor() {}
}

We also import BrowserModule into MaterialSelectorCompositionModule as the console warning suggests to do. We leave its template and everything else unchanged.

Now when viewing the composition, there are no errors but it appears things did not import correctly. Again - we haven't gotten to the styling by importing .scss, fonts, etc, but we'd expect it to at least try to render the stock components; it appears to have just dumped some text. I'll continue to try and get the styling working in the meantime, but can't help but feel something was not imported properly.

Do you have any additional advice?

Screen Shot 2022-07-06 at 10 26 35 AM
gmichael27 commented 2 years ago

I also - intermittently at first, but constantly now - get this on running bit start. I can share more info/possibly full repo if needed, only a few things have changed since my last post (trying to also have components that do NOT use the custom env to compare against). I've deleted the directory, re-cloned and encounter this now.

This appears regardless of if I've commented out the useAngularElementsPreview: true configuration as suggested:

Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
 - configuration.resolve.alias should be one of these:
   object { alias?, aliasFields?, byDependency?, cache?, cachePredicate?, cacheWithContext?, conditionNames?, descriptionFiles?, enforceExtension?, exportsFields?, extensions?, fallback?, fileSystem?, fullySpecified?, importsFields?, mainFields?, mainFiles?, modules?, plugins?, preferAbsolute?, preferRelative?, resolver?, restrictions?, roots?, symlinks?, unsafeCache?, useSyncFileSystemCalls? }
   -> Redirect module requests.
   Details:
    * configuration.resolve.alias['@angular/platform-browser-dynamic'] should be one of these:
      [non-empty string, ...] | false | non-empty string
      -> New request.
      Details:
       * configuration.resolve.alias['@angular/platform-browser-dynamic'] should be an array:
         [non-empty string, ...]
         -> Multiple alternative requests.
       * configuration.resolve.alias['@angular/platform-browser-dynamic'] should be false.
         -> Ignore request (replace with empty module).
       * configuration.resolve.alias['@angular/platform-browser-dynamic'] should be a non-empty string.
         -> New request.
This error should have never happened. Please report this issue on Github https://github.com/teambit/bit/issues