fix(vite): escape angular template html and load as strings

This commit is contained in:
Jack Westbrook
2025-03-03 09:08:14 +01:00
parent 8ad543a486
commit 11b6a42bb6
5 changed files with 74 additions and 33 deletions
+2
View File
@@ -117,6 +117,7 @@
"@types/google.analytics": "^0.0.46",
"@types/gtag.js": "^0.0.20",
"@types/history": "4.7.11",
"@types/html-minifier-terser": "^7",
"@types/ini": "^4",
"@types/jest": "29.5.14",
"@types/jquery": "3.5.32",
@@ -185,6 +186,7 @@
"eslint-scope": "^8.1.0",
"fishery": "^2.2.2",
"glob": "11.0.1",
"html-minifier-terser": "^7.2.0",
"http-server": "14.1.1",
"i18next-parser": "9.3.0",
"ini": "^5.0.0",
+1 -2
View File
@@ -1,6 +1,5 @@
import './panel/all';
// Vite: these partials still aren't working correctly so comment out for now.
// import './partials';
import './partials';
import './filters/filters';
import './services/alert_srv';
import './services/dynamic_directive_srv';
+2 -7
View File
@@ -1,10 +1,5 @@
// TODO: Vite has no require.context support yet. Attempt at a workaround below.
// let templates = (require as any).context('../', true, /\.html$/);
// templates.keys().forEach((key: string) => {
// templates(key);
// });
// See vite.config.ts angularHtmlImport function for the code that _should_ assist with these templates.
// TODO: Vite has no require.context support so we're importing them individually here.
// See the vite.config.ts angularHtmlImport plugin for the code that compiles these templates.
import 'app/angular/panel/partials/query_editor_row.html';
import 'app/angular/partials/http_settings_next.html';
+34 -22
View File
@@ -1,32 +1,11 @@
import react from '@vitejs/plugin-react-swc';
import { minify } from 'html-minifier-terser';
import { createRequire } from 'node:module';
import { fileURLToPath } from 'node:url';
import { defineConfig } from 'vite';
const require = createRequire(import.meta.url);
// This is a Vite plugin for handling angular templates.
// https://vitejs.dev/guide/api-plugin.html#simple-examples
// The webpack output from https://github.com/WearyMonkey/ngtemplate-loader looks like this:
// var code = "\n<div class=\"graph-annotation\">\n\t<div class=\"graph-annotation__header\">\n\t\t<div class=\"graph-annotation__user\" bs-tooltip=\"'Created by {{ctrl.login}}'\">\n\t\t</div>\n\n\t\t<div class=\"graph-annotation__title\">\n\t\t\t<span ng-if=\"!ctrl.event.id\">Add annotation</span>\n\t\t\t<span ng-if=\"ctrl.event.id\">Edit annotation</span>\n\t\t</div>\n\n <div class=\"graph-annotation__time\">{{ctrl.timeFormated}}</div>\n\t</div>\n\n\t<form name=\"ctrl.form\" class=\"graph-annotation__body text-center\">\n\t\t<div style=\"display: inline-block\">\n\t\t\t<div class=\"gf-form gf-form--v-stretch\">\n\t\t\t\t<span class=\"gf-form-label width-7\">Description</span>\n\t\t\t\t<textarea class=\"gf-form-input width-20\" rows=\"2\" ng-model=\"ctrl.event.text\" placeholder=\"Description\"></textarea>\n\t\t\t</div>\n\n\t\t\t<div class=\"gf-form\">\n\t\t\t\t<span class=\"gf-form-label width-7\">Tags</span>\n\t\t\t\t<bootstrap-tagsinput ng-model=\"ctrl.event.tags\" tagclass=\"label label-tag\" placeholder=\"add tags\">\n\t\t\t\t</bootstrap-tagsinput>\n\t\t\t</div>\n\n\t\t\t<div class=\"gf-form-button-row\">\n\t\t\t\t<button type=\"submit\" class=\"btn btn-primary\" ng-click=\"ctrl.save()\">Save</button>\n\t\t\t\t<button ng-if=\"ctrl.event.id && ctrl.canDelete()\" type=\"submit\" class=\"btn btn-danger\" ng-click=\"ctrl.delete()\">Delete</button>\n\t\t\t\t<a class=\"btn-text\" ng-click=\"ctrl.close();\">Cancel</a>\n\t\t\t</div>\n\t\t</div>\n\t</form>\n</div>\n";
// Exports
// var _module_exports =code;;
// var path = 'public/app/features/annotations/partials/event_editor.html';
// window.angular.module('ng').run(['$templateCache', function(c) { c.put(path, _module_exports) }]);
// module.exports = path;
function angularHtmlImport() {
return {
name: 'transform-angular-html',
transform(src, id) {
if (/^.*\.html$/g.test(id)) {
const result = `let path = '${id}'; angular.module('ng').run(['$templateCache', c => { c.put(path, \`${src}\`) }]); export default path;`;
return { code: result, map: null };
}
},
};
}
// https://vitejs.dev/config/
export default defineConfig({
root: './public',
@@ -85,3 +64,36 @@ export default defineConfig({
extensions: ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.json'],
},
});
// This is a Vite plugin for handling angular templates.
// https://vitejs.dev/guide/api-plugin.html#simple-examples
// The webpack output from https://github.com/WearyMonkey/ngtemplate-loader looks like this:
// var code = "\n<div class=\"graph-annotation\">\n\t<div class=\"graph-annotation__header\">\n\t\t<div class=\"graph-annotation__user\" bs-tooltip=\"'Created by {{ctrl.login}}'\">\n\t\t</div>\n\n\t\t<div class=\"graph-annotation__title\">\n\t\t\t<span ng-if=\"!ctrl.event.id\">Add annotation</span>\n\t\t\t<span ng-if=\"ctrl.event.id\">Edit annotation</span>\n\t\t</div>\n\n <div class=\"graph-annotation__time\">{{ctrl.timeFormated}}</div>\n\t</div>\n\n\t<form name=\"ctrl.form\" class=\"graph-annotation__body text-center\">\n\t\t<div style=\"display: inline-block\">\n\t\t\t<div class=\"gf-form gf-form--v-stretch\">\n\t\t\t\t<span class=\"gf-form-label width-7\">Description</span>\n\t\t\t\t<textarea class=\"gf-form-input width-20\" rows=\"2\" ng-model=\"ctrl.event.text\" placeholder=\"Description\"></textarea>\n\t\t\t</div>\n\n\t\t\t<div class=\"gf-form\">\n\t\t\t\t<span class=\"gf-form-label width-7\">Tags</span>\n\t\t\t\t<bootstrap-tagsinput ng-model=\"ctrl.event.tags\" tagclass=\"label label-tag\" placeholder=\"add tags\">\n\t\t\t\t</bootstrap-tagsinput>\n\t\t\t</div>\n\n\t\t\t<div class=\"gf-form-button-row\">\n\t\t\t\t<button type=\"submit\" class=\"btn btn-primary\" ng-click=\"ctrl.save()\">Save</button>\n\t\t\t\t<button ng-if=\"ctrl.event.id && ctrl.canDelete()\" type=\"submit\" class=\"btn btn-danger\" ng-click=\"ctrl.delete()\">Delete</button>\n\t\t\t\t<a class=\"btn-text\" ng-click=\"ctrl.close();\">Cancel</a>\n\t\t\t</div>\n\t\t</div>\n\t</form>\n</div>\n";
// Exports
// var _module_exports =code;;
// var path = 'public/app/features/annotations/partials/event_editor.html';
// window.angular.module('ng').run(['$templateCache', function(c) { c.put(path, _module_exports) }]);
// module.exports = path;
function angularHtmlImport() {
return {
name: 'transform-angular-html',
async transform(src, id) {
if (/^.*\.html$/g.test(id)) {
const minifierOptions = {
collapseWhitespace: false,
collapseBooleanAttributes: true,
conservativeCollapse: true,
minifyJS: true,
};
const idParts = id.split('/public/');
const path = idParts[idParts.length - 1];
const html = await minify(src, minifierOptions);
const stringified = JSON.stringify(html);
const result = `let path = 'public/${path}'; angular.module('ng').run(['$templateCache', c => { c.put(path, ${stringified}) }]); export default path;`;
return { code: result, map: null };
}
},
};
}
+35 -2
View File
@@ -10279,6 +10279,13 @@ __metadata:
languageName: node
linkType: hard
"@types/html-minifier-terser@npm:^7":
version: 7.0.2
resolution: "@types/html-minifier-terser@npm:7.0.2"
checksum: 10/312d9285b034538fc8c851d99b5cc47f83e802f0dccc1be4adf10d4ee881d92d14f97ca1862313a827b844a7f71db6ab80198d7741f1747a9bcf7de77181ce40
languageName: node
linkType: hard
"@types/ini@npm:^4":
version: 4.1.1
resolution: "@types/ini@npm:4.1.1"
@@ -13413,7 +13420,7 @@ __metadata:
languageName: node
linkType: hard
"clean-css@npm:^5.1.5":
"clean-css@npm:^5.1.5, clean-css@npm:~5.3.2":
version: 5.3.3
resolution: "clean-css@npm:5.3.3"
dependencies:
@@ -13743,6 +13750,13 @@ __metadata:
languageName: node
linkType: hard
"commander@npm:^10.0.0":
version: 10.0.1
resolution: "commander@npm:10.0.1"
checksum: 10/8799faa84a30da985802e661cc9856adfaee324d4b138413013ef7f087e8d7924b144c30a1f1405475f0909f467665cd9e1ce13270a2f41b141dab0b7a58f3fb
languageName: node
linkType: hard
"commander@npm:^12.1.0":
version: 12.1.0
resolution: "commander@npm:12.1.0"
@@ -18257,6 +18271,7 @@ __metadata:
"@types/google.analytics": "npm:^0.0.46"
"@types/gtag.js": "npm:^0.0.20"
"@types/history": "npm:4.7.11"
"@types/html-minifier-terser": "npm:^7"
"@types/ini": "npm:^4"
"@types/jest": "npm:29.5.14"
"@types/jquery": "npm:3.5.32"
@@ -18355,6 +18370,7 @@ __metadata:
fishery: "npm:^2.2.2"
glob: "npm:11.0.1"
history: "npm:4.10.1"
html-minifier-terser: "npm:^7.2.0"
http-server: "npm:14.1.1"
i18next: "npm:^24.0.0"
i18next-browser-languagedetector: "npm:^8.0.0"
@@ -18826,6 +18842,23 @@ __metadata:
languageName: node
linkType: hard
"html-minifier-terser@npm:^7.2.0":
version: 7.2.0
resolution: "html-minifier-terser@npm:7.2.0"
dependencies:
camel-case: "npm:^4.1.2"
clean-css: "npm:~5.3.2"
commander: "npm:^10.0.0"
entities: "npm:^4.4.0"
param-case: "npm:^3.0.4"
relateurl: "npm:^0.2.7"
terser: "npm:^5.15.1"
bin:
html-minifier-terser: cli.js
checksum: 10/7320095dbf08c361b45e855bd840d1d21fe86326afee775503594163532ebaaed9bb1c9dc98232b03c169dc24b56f30c294d559bca0cade59f9c950a1992db82
languageName: node
linkType: hard
"html-parse-stringify@npm:^3.0.1":
version: 3.0.1
resolution: "html-parse-stringify@npm:3.0.1"
@@ -29466,7 +29499,7 @@ __metadata:
languageName: node
linkType: hard
"terser@npm:^5.17.4, terser@npm:^5.26.0, terser@npm:^5.7.2":
"terser@npm:^5.15.1, terser@npm:^5.17.4, terser@npm:^5.26.0, terser@npm:^5.7.2":
version: 5.29.2
resolution: "terser@npm:5.29.2"
dependencies: