angularweb-componentesbuildangular-elementsstackblitz

How simply remove hash from chunk in Angular with esbuild


After updating Angular to version 17 with esbuild, a hash is added to the chunk name. The problem is that if you want to use lazy loading for Angular-elements, then in development mode a hash will be added for each chunk.

I cannot use the script directly in HTML, since the name changes after component update.

The question is how can I remove the hash from the names of chunks?

// before update
<script src="http://localhost:4200/src_components_test_test_component_ts.js" type="module"></script>

// after
<script src="http://localhost:4200/test-[hash].js" type="module"></script>

enter image description here

On stackblitz now default container with esbuild example

I try use NamedChunk options, but in code esbuilders config hash is part of naming EsBuilder naming


Solution

  • I would not recommend dealing with the lazy loaded code inside the html.

    Angular delivers options to load lazy components by rendering them into ng-templates in outlet refs.

    Also removing output hashing is not recommended since it interferes with the accuracy of version deployment and browser cashing. You should only require the main.js of your angular elements.

    Let me give you an example of dealing with lazy loaded code.

    First we need a simple component, that has nothing but a outlet for the lazy component:

    
    import { CommonModule } from '@angular/common';
    import { Component, Input, OnInit, ViewChild, ViewContainerRef } from '@angular/core';
    
    @Component({
      selector: 'app-lazy-wrapper',
      standalone: true,
      imports: [CommonModule],
      template: `<ng-template #outletRef></ng-template>`,
    })
    export class LazyWrapperComponent implements OnInit {
      // the place where the lazy component should be rendered
      @ViewChild('outletRef', { read: ViewContainerRef })
      public outletRef!: ViewContainerRef;
    
      // provide an input to allow the component to be selected
      @Input()
      public componentName = '';
    
      constructor(private injector: Injector) {}
    
      // define a lazy import map that points to a function that will lazily import a given component
      private lazyComponentMap: Record<string, (() => Promise<any>) | undefined> = {
        test: () =>
          import('../lazy-component/lazy-component.component').then(
            (module) => module.LazyComponentComponent
          ),
      };
    
      ngOnInit(): void {
        // on init the input of the component name should be picked here
        const lazyComp = this.lazyComponentMap[this.componentName];
        if (lazyComp) {
          // if a valid object exists, execute the import, and render the component in the outlet ref
          lazyComp().then((component) => {
            this.outletRef.createComponent(component, {
              // also use the injector so that the context is accurate
              injector: this.injector,
            });
          });
        }
      }
    }
    
    

    Now lets define a small component that we want to load lazy in angular elmenets.

    
    import { Component } from '@angular/core';
    
    @Component({
      selector: 'app-lazy-component',
      standalone: true,
      imports: [],
      template: `<h1 style="color:purple;"> I am a lazy component </h1>`,
    })
    export class LazyComponentComponent {
    }
    
    
    

    Then we import and define the lazywrapper as a custom element in our main.ts for angular elements:

    
    import {createCustomElement} from "@angular/elements";
    import { LazyWrapperComponent } from "../lazyStuff/lazy-wrapper/lazy-wrapper.component";
    import { createApplication } from "@angular/platform-browser";
    
    (async () => {
      const app = await createApplication({providers: [
        ]});
    
      const el = createCustomElement(LazyWrapperComponent, {
        injector: app.injector,
      })
      customElements.define("angular-lazy-wrapper", el);
    })()
    
    

    Now to the important part: Accessing the component and loading in the html of the hosts .html document.

    
    <!DOCTYPE html>
    <html lang="en" data-critters-container>
      <head>
        <title>My app</title>
        <meta charset="UTF-8">
      <body>
      <script src="polyfills.js" type="module"></script>
      <script src="main.js" type="module"></script>
    
      <!-- Important inputs in cusom elements are snake cased -->
      <angular-lazy-wrapper component-name="test"></angular-lazy-wrapper>
      <!-- we pass "test" as the accessor to the lazy import -->
    </body>
    </html>
    
    

    No chunk import is required, only the main.js and maybe polyfills. This will now load the component lazily and render it.

    A small implementation of this approach can be found here: https://stackblitz.com/edit/stackblitz-starters-btfhte?file=package.json

    run: "npm run build:elements" "npm preview:elements"

    And see the results.

    For more references on ng-template and bindings as ViewContainerRef for lazy components read some documentation here: https://angular.io/api/core/ViewContainerRef#description

    And also "createComponent" documentation: https://angular.io/api/core/createComponent