I’m developing a custom Angular builder to remove specific code blocks from both TypeScript and HTML files during the build process. While the TypeScript processing works correctly, I’m unable to get the HTML processing working.
What I Want to Achieve
-
Remove code blocks from TypeScript files that are wrapped in:
//#REMOVE_BLOCK_START // some code here //#REMOVE_BLOCK_END
-
Remove code blocks from HTML files that are wrapped in:
<!-- #REMOVE_BLOCK_START --> <!-- some html here --> <!-- #REMOVE_BLOCK_END -->
Project Structure
my-project/
├── client/ # Angular application
│ ├── angular.json
│ └── src/
└── custom-builder/ # Custom builder implementation
├── src/
│ ├── custom-builder.ts
│ ├── HtmlContentModifierPlugin.ts
│ └── remove_module.ts
├── builders.json
└── package.json
Code Implementation
- Custom Builder (custom-builder.ts):
import * as path from 'path';
import * as webpack from 'webpack';
import { BuilderContext, createBuilder } from '@angular-devkit/architect';
import { executeBrowserBuilder } from '@angular-devkit/build-angular';
import { HtmlContentModifierPlugin } from './HtmlContentModifierPlugin';
function transform(input: webpack.Configuration): webpack.Configuration {
if (!input.module?.rules) return input;
// Add rule for TypeScript and HTML files
// __ditname refers as 'my-project/custom-builder/dist
// (since the remove_module.ts will be converted in remove_moduel.js we are passing it in loader)
const newRule = {
test: /.(ts|html)$/,
exclude: /node_modules/,
use: [
{
loader: path.resolve(__dirname, 'remove_module.js'),
options: {}
}
]
};
input.module.rules.unshift(newRule);
// Add HTML plugin
if (!input.plugins) {
input.plugins = [];
}
input.plugins.push(new HtmlContentModifierPlugin());
return input;
}
function executeBuilder(options: any, context: BuilderContext) {
return executeBrowserBuilder(options, context, {
webpackConfiguration: transform
});
}
export default createBuilder(executeBuilder);
- HTML Plugin (HtmlContentModifierPlugin.ts):
import { Compiler, sources } from 'webpack';
import { Compilation } from 'webpack';
export class HtmlContentModifierPlugin {
apply(compiler: Compiler): void {
compiler.hooks.emit.tapAsync(
'HtmlContentModifierPlugin',
(compilation: Compilation, callback) => {
// Process all compiled assets
Object.keys(compilation.assets).forEach((filename) => {
if (filename.endsWith('.html')) {
console.log('Processing HTML file:', filename);
const asset = compilation.assets[filename];
const content = asset.source().toString();
// Remove assessment blocks
const modifiedContent = content.replace(
/<!--s*#REMOVE_BLOCK_STARTs*-->[sS]*?<!--s*#REMOVE_BLOCK_ENDs*-->/g,
''
);
compilation.assets[filename] = new sources.RawSource(modifiedContent);
}
});
callback();
}
);
}
}
- remove_module.ts:
import { LoaderDefinitionFunction } from 'webpack';
const removeAssessmentLoader: LoaderDefinitionFunction = function (source) {
const filename = this.resourcePath;
console.log("Code is running with loader for:", filename);
// Replace all occurrences of "originalText" with an empty string
if (filename.endsWith('.ts')) {
return source.replace(///#REMOVE_BLOCK_START[sS]*?//#REMOVE_BLOCK_END/g, '');
}
// Remove HTML code between <!-- #REMOVE_BLOCK_START --> and <!-- #REMOVE_BLOCK_END -->
else if (filename.endsWith('.html')) {
console.log("HTML CHANGE");
return source.replace(/<!--s*#REMOVE_BLOCK_STARTs*-->[sS]*?<!--s*#REMOVE_BLOCK_ENDs*-->/g, '');
}
};
export default removeAssessmentLoader;
- custom-builder/package.json:
{
"name": "custom-builder",
"version": "0.0.1",
"builders": "./builders.json",
"scripts": {
"build": "tsc"
},
"devDependencies": {
"@angular-devkit/architect": "0.1602.16",
"@angular-devkit/build-angular": "^16.2.16",
"@angular-devkit/core": "^16.2.16",
"@types/karma": "^6.3.9",
"@types/node": "~18.11.6",
"karma": "^6.4.1",
"rxjs": "~7.5.0",
"typescript": "^5.1.6"
},
"dependencies": {
"@angular-devkit/schematics": "^16.2.16",
"@angular/cli": "^16.2.16"
}
}
- angular.json Configuration.
{
"projects": {
"client": {
"architect": {
"custom-target": {
"builder": "@angular-builders/custom-webpack:browser",
"options": {
"outputPath": "dist/client",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.app.json"
},
"configurations": {
"production": {
"optimization": true,
"outputHashing": "none",
"sourceMap": false,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true
}
}
}
}
}
}
}
What’s Working
The TypeScript processing works perfectly. All code blocks between //#REMOVE_BLOCK_START and //#REMOVE_BLOCK_END are successfully removed during build.
What is Not Working
The HTML processing is not working. The HTML files are not being processed, and the code blocks between <!– #REMOVE_BLOCK_START –> and <!– #REMOVE_BLOCK_END –> remain in the output
Questions
- Why isn’t the HTML plugin processing the HTML files?
- Is there a better approach to process HTML files during the Angular build process?
( I have heard about AngularWebpackPlugin but i’m not sure if it has anything like transform to perform function at compilation time)