I am working on multi-module Gradle project having below structure
parent-app
- backend
- src/main
- java
- resources
- webapp
- build.gradle
- angular-ui
- src
- app
- envirnoments
- index.html
- index.ftl
- main.ts
- angular.json
- package.json
- webpack.config.js
- build.gradle
- build.gradle
- settings.gradle
I am using index.ftl
(freemarker templates) as my view which uses some macros from in-house library (gradle dependencies) to get headers. But for everything else, I have to use angular components/pages.
I was trying with webpack
to dynamically add angular bundles (main.js, polyfill.js etc) in index.ftl
file.
The configuration throws minify error
at angular build (ng build --prod
) but I see js files are added in index.ftl as scripts.
Can anyone please help understand the issue and how to resolve it so that my angular bundle are fully loaded in index.ftl file without any errors.
Below is the error
Html Webpack Plugin:
<pre>
Error: html-webpack-plugin could not minify the generated output.
In production mode the html minifcation is enabled by default.
If you are not generating a valid html output please disable it manually.
You can do so by adding the following setting to your HtmlWebpackPlugin config:
|
| minify: false
|
See https://github.com/jantimon/html-webpack-plugin#options for details.
For parser dedicated bugs please create an issue here:
https://danielruf.github.io/html-minifier-terser/
Parse Error: <#macro main>
<app-root></app-root>
<#macro>
<#maro pagebody>
<#macro><script src="angular-ui/runtime.689a94f98876eea3f04c.js"></script><script src="angular-ui/polyfills.94daefd414b8355106ab.js"></script><script src="angular-ui/main.95a9937db670e12d53ac.js"></script>
- htmlparser.js:244 new HTMLParser
[angular-webpack]/[html-minifier-terser]/src/htmlparser.js:244:13
- htmlminifier.js:993 minify
[angular-webpack]/[html-minifier-terser]/src/htmlminifier.js:993:3
- htmlminifier.js:1354 Object.exports.minify
[angular-webpack]/[html-minifier-terser]/src/htmlminifier.js:1354:16
- index.js:1019 HtmlWebpackPlugin.minifyHtml
[angular-webpack]/[html-webpack-plugin]/index.js:1019:46
- index.js:435 HtmlWebpackPlugin.postProcessHtml
[angular-webpack]/[html-webpack-plugin]/index.js:435:40
- index.js:260
[angular-webpack]/[html-webpack-plugin]/index.js:260:25
- task_queues:96 processTicksAndRejections
node:internal/process/task_queues:96:5
</pre>
webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const fs = require('fs');
const path = require('path');
module.exports = {
output: {
"publicPath": "angular-ui/"
},
plugins: [
new HtmlWebpackPlugin({
"template": "./src/index.ftl",
"filename": "../backend/src/main/webapp/WEB-INF/templates/index.ftl",
"inject": false,
"hash": true,
"xhtml": true,
}),
{
apply: (compile) => {
compile.hooks.afterEmit.tap('AfterEmitPlugin', (compilation) => {
fs.unlink(path.join(process.cwd(), "../backend/src/main/webapp/angular-ui/index.html"), (error) => {
if (error) throw error;
});
});
}
}
]
}
index.ftl
<#macro main>
<app-root></app-root>
<#macro>
<#maro pagebody>
<% for (key in htmlWebpackPlugin.files.chunks) { %>
<% if (htmlWebpackPlugin.files.chunks[key].entry) { %>
<script src="<@spring.url '/<%= htmlWebpackPlugin.files.chunks[key].entry %>'/>" type="text/javascript"></script>
<% } %>
<% } %>
<#macro>
<@header>
index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>AngularUI</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>
<app-root></app-root>
</body>
</html>
package.json
{
"name": "angular-ui",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve --open",
"build": "ng build --prod",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},
"private": true,
"dependencies": {
"@angular/animations": "~11.2.14",
"@angular/common": "~11.2.14",
"@angular/compiler": "~11.2.14",
"@angular/core": "~11.2.14",
"@angular/forms": "~11.2.14",
"@angular/platform-browser": "~11.2.14",
"@angular/platform-browser-dynamic": "~11.2.14",
"@angular/router": "~11.2.14",
"rxjs": "~6.6.0",
"tslib": "^2.0.0",
"zone.js": "~0.11.3"
},
"devDependencies": {
"@angular-builders/custom-webpack": "^11.1.1",
"@angular-devkit/build-angular": "~0.1102.17",
"@angular/cli": "~11.2.17",
"@angular/compiler-cli": "~11.2.14",
"@types/jasmine": "~3.6.0",
"@types/node": "^12.11.1",
"codelyzer": "^6.0.0",
"html-webpack-plugin": "^4.5.2",
"jasmine-core": "~3.6.0",
"jasmine-spec-reporter": "~5.0.0",
"karma": "~6.1.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage": "~2.0.3",
"karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "~1.5.0",
"protractor": "~7.0.0",
"ts-node": "~8.3.0",
"tslint": "~6.1.0",
"typescript": "~4.1.5"
}
}
angular.json
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"angular-ui": {
"projectType": "application",
"schematics": {
"@schematics/angular:application": {
"strict": true
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-builders/custom-webpack:browser",
"options": {
"customWebpackConfig": {
"path": "./webpack.config.js"
},
"outputPath": "../backend/src/main/webapp/angular-ui",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"aot": true,
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.css"
],
"scripts": []
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
]
}
}
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "angular-ui:build"
},
"configurations": {
"production": {
"browserTarget": "angular-ui:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "angular-ui:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.css"
],
"scripts": []
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"tsconfig.app.json",
"tsconfig.spec.json",
"e2e/tsconfig.json"
],
"exclude": [
"**/node_modules/**"
]
}
},
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "angular-ui:serve"
},
"configurations": {
"production": {
"devServerTarget": "angular-ui:serve:production"
}
}
}
}
}
},
"defaultProject": "angular-ui"
}
The problem is the HtmlWebpackPlugin
doesn't know how to correctly parse .ftl
files. By default the plugin will use an ejs-loader
. See https://github.com/jantimon/html-webpack-plugin/blob/main/docs/template-option.md
Do you need to minify the index.ftl file? I'd argue that you don't. It's not necessary especially when you can just compress it before sending it from the server. You should be able to pass the config property minify
with the value of false
into the HtmlWebpackPlugin
to prevent the minification error.
i.e.
new HtmlWebpackPlugin({
"template": "./src/index.ftl",
"filename": "../backend/src/main/webapp/WEB-INF/templates/index.ftl",
"inject": false,
"hash": true,
"xhtml": true,
"minify": false // <-- property to add
}),
Adding the minify: false
entry to the HtmlWebpackPlugin
options should fix your immediate error.
However, I also noticed the index.ftl
has a syntax error where you are trying to set the src
attribute. There is an extra '/>
before closing the src
attribute. Specifically, you'll need to modify this line:
<script src="<@spring.url '/<%= htmlWebpackPlugin.files.chunks[key].entry %>'/>" type="text/javascript"></script>
to be:
<script src="<@spring.url '/<%= htmlWebpackPlugin.files.chunks[key].entry %>" type="text/javascript"></script>
Additionally, when testing locally, in order for my js files to be written to the file, I needed to change your line htmlWebpackPlugin.files.chunks
to htmlWebpackPlugin.files.js
because I'm not doing any chunking. You may need to do the same.