angular-cli icon indicating copy to clipboard operation
angular-cli copied to clipboard

Injecting Custom Template Loading Logic

Open lekhmanrus opened this issue 1 year ago • 8 comments

Command

build, serve

Description

In previous Angular versions, when using webpack-based builders, we had the ability to customize how component templates were processed (e.g., using third-party loaders like ngx-pug-builders to support Pug templates). With ESBuild, template loading logic to be managed internally by the AdapterResourceLoader. It looks, like currently there appears to be no way to inject custom logic into the template loading process. This limits the ability for third-party libraries to provide support for alternative template formats or to apply custom modifications during the build.

Describe the solution you'd like

It would be beneficial for Angular to expose a mechanism for developers to hook into or modify the HTML loading logic. This would enable third-party libraries to support template engines like Pug or apply other transformations.

While Angular CLI may not want to handle all possible template engines directly, allowing developers to customize the HTML loading process would open the door for greater flexibility and extensibility within the ecosystem.

Describe alternatives you've considered

No response

lekhmanrus avatar Oct 08 '24 23:10 lekhmanrus

This feature request is now candidate for our backlog! In the next phase, the community has 60 days to upvote. If the request receives more than 20 upvotes, we'll move it to our consideration list.

You can find more details about the feature request process in our documentation.

angular-robot[bot] avatar Oct 09 '24 13:10 angular-robot[bot]

Can you provide additional information regarding your use cases that are not met by using Angular templates? While the Angular template language can be parsed as valid HTML, its full syntactic space goes beyond HTML. This is especially true with the control flow, defer, let, etc. capabilities now present. Additionally, the developer ecosystem tools rely on Angular components using the Angular template language and includes schematics, updates, linting (eslint), formatting (prettier), IDE integration (Angular language service or WebStorm), and more.

clydin avatar Oct 10 '24 19:10 clydin

Hi @clydin, I understand that integrating and supporting pug files isn't part of the Angular team's current plans, but a significant number of developers still use Pug in their projects. I had previously integrated the webpack pug loader via a custom Angular builders, but since Angular switched to esbuild and vite (which btw works perfectly and fast), integrating pug into Angular has become impossible. This seems to be because templates are loaded through AdapterResourceLoader (as I understand), and any esbuild plugins are ignored at that stage. I also tried importing pug files using a loader kinda:

import template from './app.component.pug' with { loader: 'text' };

However, using this in the @Component's template is also not possible. Regarding control flow, defer, let, and similar capabilities, it's worth noting that they can still be used in pug templates as well. So perhaps it would be good to have some ability to inject into the template loading (to compile pug to html in this specific case)? Or maybe there is some better workaround.

P.S.: Personally I do not use Pug in my Angular projects anymore - just trying to support a library that some developers still seem to use.

lekhmanrus avatar Oct 10 '24 20:10 lekhmanrus

To be clear, it's not about replacing the Angular templating functionality with Pug's, but rather using pure, non-templating Pug syntax with Angular, for example:

<div *ngIf="thing" class="dropdown-menu-right" ngbDropdownMenu>
   <a class="dropdown-item" (click)="setFilterMode(mode)">
       <i class="far fa-fw fa-tilde"></i>
       <span translate>{{mode}}</span>
    </a>
</div>

becomes

.dropdown-menu-right(*ngIf='thing', ngbDropdownMenu)
    a.dropdown-item((click)='setFilterMode(mode)')
        i.far.fa-fw.fa-tilde
        span(translate) {{mode}}

All this requires really is a single build callback that provides a chance to modify the raw template (as loaded from templateUrl) before it's used for the build.

That already works with angular-builders/custom-webpack but not esbuild

Eugeny avatar Oct 10 '24 21:10 Eugeny

Btw, I only use basic features of Pug to combine with Angular template. That's only use shortcut syntax of Pug and use all logic flow from Angular template. It help me get a clean code, write less and read fast and debug easier. So I don't care the logic control feature of Pug or other high level feature, can we have alternatives? It looks like not a complex task, ex: convert . to wrapped div tags, convert a to wrapped a tags. Sometimes I think maybe I can extract a new lib from Pug to do only basic converting tasks. Unfortunately I'm not a good js lib coder. Another reason why I prefer such syntax is Emacs is my editor and I don't use plugins to auto convert html tags. I know it's not a complex to use html plugin, I just comfortable with the css style html code. Write less not only just good for write, but also good for read and debug.

raykin avatar Dec 31 '24 07:12 raykin

So... I just had to roll it myself, and you can too with this patch-package patch:

patches/@angular+compiler+17.3.11.patch:

diff --git a/node_modules/@angular/compiler/fesm2022/compiler.mjs b/node_modules/@angular/compiler/fesm2022/compiler.mjs
index 521c187..778aa67 100755
--- a/node_modules/@angular/compiler/fesm2022/compiler.mjs
+++ b/node_modules/@angular/compiler/fesm2022/compiler.mjs
@@ -30263,6 +30263,9 @@ function getTextInterpolationExpression(interpolation) {
             return Identifiers.textInterpolateV;
     }
 }
+
+import { compile as compilePug } from 'pug'
+
 /**
  * Parse a template into render3 `Node`s and additional metadata, with no other dependencies.
  *
@@ -30271,6 +30274,10 @@ function getTextInterpolationExpression(interpolation) {
  * @param options options to modify how the template is parsed
  */
 function parseTemplate(template, templateUrl, options = {}) {
+    if (templateUrl.endsWith('.pug')) {
+        console.log('Compiling', templateUrl)
+        template = compilePug(template, { doctype: 'html', pretty: true })()
+    }
     const { interpolationConfig, preserveWhitespaces, enableI18nLegacyMessageIdFormat } = options;
     const bindingParser = makeBindingParser(interpolationConfig);
     const htmlParser = new HtmlParser();

Eugeny avatar Mar 27 '25 17:03 Eugeny

I would love this feature too. Using the custom webpack builder, I'm currently using a webpack plugin to inline SVG files into templates (SVGs for things like company logos, etc). This allows the SVG files to be used in multiple places without having to duplicate them in each template.

reduckted avatar Sep 20 '25 07:09 reduckted

Thanks @Eugeny. You're a lifesaver.

I ran into another issue while trying tests with Vitest, seems the TestBed will put a dummy value for templateUrl, e.g. ng:///MyComponent2/template.html. so I just add an extra condition to it:

if (templateUrl.endsWith('.pug') || templateUrl.endsWith('/template.html')) {
...
}

iblislin avatar Oct 14 '25 06:10 iblislin