Как смешать Angular Elements с подходом "ng g library"?

Как вы знаете, подход "ng g library" помогает нам в создании библиотек повторно используемых компонентов. Но что, если бы я хотел, чтобы эти компоненты были скомпилированы в веб-компоненты... через поддержку Angular Elements? Не только это, но и то, что каждый компонент из библиотеки должен был быть скомпилирован в свою собственную папку или файл JS. Как настроить среду разработки, которая позволила бы мне добиться этого?

Например:

Если я создаю Lib и добавляю пользовательский компонент, я знаю, что могу скомпилировать его, сгенерировав ряд папок, таких как esm5, esm2015, fesm5 и т.д. Теперь возникает вопрос: как добавить, скажем, 30 пользовательских компонентов в мою библиотеку, а затем, когда я скомпилирую, для каждого из них будет создана папка, содержащая их версию веб-компонента... как будто Angular Elements проверила мою библиотеку компонентов и сгенерировала версию каждого из них.,

Как это:

lib/
lib/custom/comp1
lib/custom/comp2
lib/custom/comp3
lib/custom/comp4

Превращается во что-то похожее на:

Dist/
Dist/custom/
Dist/custom/bundle
Dist/custom/esm5
Dist/custom/esm2015
Dist/custom/comp1_web_independend_component_version
Dist/custom/comp2_web_independend_component_version
Dist/custom/comp3_web_independend_component_version
Dist/custom/comp4_web_independend_component_version

Ближайшее решение, которое я нашел, это:

https://medium.com/@rahulsahay19/running-multiple-angular-elements-on-the-same-page-5949dac5523

Я также попросил команду Angular CLI помочь с этим:

https://github.com/angular/angular-cli/issues/13999

Ответ 1

Я считаю, что причина, по которой ваш вопрос не получил достаточного внимания, связана с тем, что он так неоднозначен. Как отметил маршал, в сети есть много замечательных ресурсов (из которых он перечисляет один), чтобы помочь людям начать работу с Angular Elements. Вот ссылка на один, который я нашел очень полезным.

Предполагая, что вы хотите создать библиотеку этих элементов, следуя ссылкам на статьи, это кажется довольно простым. Не зная, что вы подразумеваете под "настройкой среды разработки", вы затрудняетесь ответить на этот вопрос. Вот шаги, которые я предпринял, чтобы создать то, что я считаю средой разработки для создания этих пользовательских компонентов.

Нужно установить Cli, только если он не установлен глобально. (Я предпочитаю устанавливать его локально и ссылаться на него так. Если он установлен глобально, то замените node_modules/.bin/ng в приведенных ниже командах просто ng).

npm install @angular/cli
node_modules/.bin/ng new custom-elements-dev-env
cd custom-elements-dev-env
node_modules/.bin/ng add @angular/elements
node_modules/.bin/ng g library custom-elements-lib --prefix custom

Затем я обновил файл projects/custom-elements-lib/src/lib/custom.module.ts:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule, Injector, DoBootstrap } from '@angular/core';
import { createCustomElement } from '@angular/elements';
import { ButtonComponent } from './button.component';

@NgModule({
  declarations: [ButtonComponent],
  imports: [BrowserModule],
  entryComponents: [ButtonComponent]
})
export class CustomModule implements DoBootstrap {
  constructor(injector: Injector) {
    const customButton = createCustomElement(ButtonComponent, { injector });
    customElements.define('custom-button', customButton);
  }

  ngDoBootstrap() {}
}

Файл projects/custom-elements-lib/src/lib/button.component.ts почти такой же, как и в связанной статье:

import { Component, ViewEncapsulation, Input, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'custom-button',
  template: '<button (click)="handleClick()">{{label}}</button>',
  styles: ['
  button {
    border: solid 3px;
    padding: 8px 10px;
    background: tomato;
    font-size: 20px;
  }
  '],
  encapsulation: ViewEncapsulation.Native
})
export class ButtonComponent {
  @Input() label = 'default label';
  @Output() action = new EventEmitter<number>();

  /* tslint:disable-next-line:variable-name */
  private _clickCount = 0;

  handleClick() {
    this._clickCount++;
    this.action.emit(this._clickCount);
  }
}

Файл projects/custom-elements-lib/src/lib/public-api.ts был обновлен до:

export * from './lib/custom.module';
export * from './lib/button.component';

В package.json я добавил скрипт npm для сборки библиотеки: "build:custom": "ng build custom-elements-lib"

src/app/app.module.ts был обновлен, чтобы src/app/app.module.ts эту библиотеку:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { CustomModule } from 'custom';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    CustomModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Следующим шагом было обновление файла src/index.html:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>ElementsDemo</title>
  <base href="/">

  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
  <custom-button label="First Value"></custom-button>
  <script>
    const button = document.querySelector('custom-root');
    button.addEventListener('action', (event) => {
      console.log(''action' emitted: ${event.detail}');
    });
    setTimeout(() => button.label = 'Second Value', 3000);
  </script>
</body>
</html>

Теперь, если вы запустите npm run build:custom && npm run start вы должны увидеть следующую ошибку:

Не удалось создать HTMLElement: используйте оператор 'new', этот конструктор объекта DOM нельзя вызвать как функцию.

Это можно исправить, обновив файл tsconfig.json в корне проекта. Измените "target": "es5" на "target": "es2015".

Если вы остановите запуск проекта и запустите его снова, он должен запустить браузер, и теперь вы увидите свой пользовательский элемент на странице.

Есть некоторые предостережения по этому поводу, которые, однако, влияют на "среду разработки". Если вы обновите файл в своей пользовательской библиотеке, вам придется вручную вызвать метод сборки для него, чтобы браузер обновил ваши изменения.

Другое состоит в том, что для фактического использования этого компонента в неангулярном проекте вам нужно будет создать шаг сборки/пакета, как описано в указанной статье, следующим образом:

"build": "ng build --prod --output-hashing=none",
"package": 
    "cat dist/elements-demo/{runtime,polyfills,scripts,main}.js
     | gzip > elements.js.gz",
"serve": "http-server --gzip"

Это делается на уровне проекта, так как необходимо включить некоторые из библиотек Angular, которые в настоящее время исключены из сборки библиотеки.

Надеюсь это поможет. Пожалуйста, укажите больше того, что вы пытались и чего именно пытаетесь достичь, если хотите получить ответ на конкретную среду и цели.

Ответ 2

ng build внутренне использует webpack для сборки. Таким образом, эта проблема фактически разбивается на две части.

  1. Без ng eject, как подключиться к внутреннему webpackConfig и настроить его в соответствии с нашими потребностями.
  2. Как выглядит желаемый webpackConfig для этого варианта использования.

Для первой части есть решение @angular-builders/custom-webpack. По сути, это позволяет вам объединить некоторое дополнительное поле с внутренним webpackConfig и по-прежнему использовать официальный "@angular-devkit/build-angular: browser" в качестве компоновщика.

Теперь для второй части ваш вариант использования - это просто проблема сборки с несколькими входами и несколькими выходами в веб-пакете. Решение довольно простое.

const partialWebpackConfig = {
  entry: {
    'custom/comp1': '/path/to/src/lib/custom/comp1.ts',
    'custom/comp2': '/path/to/src/lib/custom/comp2.ts',
  },
  output: {
    path: path.resolve(__dirname, 'Dist'),
    filename: '[name].js'
  }
}

Ниже приведена пошаговая инструкция по настройке этого конфига.

  1. npm install @angular-builders/custom-webpack
  2. создайте webpack.config.js в корне вашего проекта:
const path = require('path');
module.exports = {
  entry: {
    'custom/comp1': path.resolve(__dirname, 'src/lib/custom/comp1.ts'),
    'custom/comp2': path.resolve(__dirname, 'src/lib/custom/comp2.ts')
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js'
  }
}
  1. отредактируйте поле "architect.build" в angular.json:
{
  // ...
  "architect": {
    "build": {
      "builder": "@angular-builders/custom-webpack:browser",
      "options": {
        "customWebpackConfig": {
          "path": "./webpack.config.js",
        },
        // ...
  1. запустите ng build, должен увидеть результат.

Для дальнейшего использования стоит упомянуть, что @angular-builders/custom-webpack поддерживает экспорт конфигурации webpack как функцию, чтобы получить полный контроль над конечным используемым webpackConfig.