diff --git a/package.json b/package.json index 7df419ce..f8f7b820 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "@eslint/js": "9.13.0", "@intlify/eslint-plugin-vue-i18n": "^3.0.0", "@types/ackee-tracker": "5.0.4", + "@types/html-minifier-terser": "7.0.2", "@types/node": "22.8.1", "@types/papaparse": "5.3.15", "@typescript-eslint/parser": "8.11.0", @@ -45,6 +46,7 @@ "eslint-plugin-import-x": "4.3.1", "eslint-plugin-prettier": "5.2.1", "eslint-plugin-vue": "9.29.1", + "html-minifier-terser": "7.2.0", "prettier": "3.3.3", "sass": "1.79.6", "typescript": "5.6.3", diff --git a/plugins/vite-plugin-minify-html/minifyHtmlPlugin.ts b/plugins/vite-plugin-minify-html/minifyHtmlPlugin.ts new file mode 100644 index 00000000..d48eabdf --- /dev/null +++ b/plugins/vite-plugin-minify-html/minifyHtmlPlugin.ts @@ -0,0 +1,22 @@ +import { Plugin } from 'vite'; +import { minify } from 'html-minifier-terser'; + +export const minifyHtmlPlugin = (): Plugin => ({ + name: 'vite-plugin-minify', + apply: 'build', + transformIndexHtml: { + order: 'post', + handler(html) { + return minify(html, { + removeComments: true, + collapseWhitespace: true, + collapseBooleanAttributes: true, + removeAttributeQuotes: false, + removeEmptyAttributes: true, + minifyCSS: true, + minifyJS: true, + minifyURLs: true + }); + } + } +}); diff --git a/plugins/vite-plugin-minify-json/minifyJsonPlugin.ts b/plugins/vite-plugin-minify-json/minifyJsonPlugin.ts new file mode 100644 index 00000000..fccfb333 --- /dev/null +++ b/plugins/vite-plugin-minify-json/minifyJsonPlugin.ts @@ -0,0 +1,24 @@ +import { Plugin } from 'vite'; + +export const minifyJsonPlugin = (): Plugin => ({ + name: 'vite-plugin-minify-json', + apply: 'build', + enforce: 'pre', + async generateBundle(_, bundle) { + for (const fileName of Object.keys(bundle)) { + if (fileName.endsWith('.json')) { + const asset = bundle[fileName]; + + if (asset.type !== 'asset') { + continue; + } + + if (typeof asset.source === 'string') { + asset.source = JSON.stringify(JSON.parse(asset.source)); + } else { + asset.source = JSON.stringify(JSON.parse(Buffer.from(asset.source).toString('utf8'))); + } + } + } + } +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 51eee3eb..24143b38 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -42,6 +42,9 @@ importers: '@types/ackee-tracker': specifier: 5.0.4 version: 5.0.4 + '@types/html-minifier-terser': + specifier: 7.0.2 + version: 7.0.2 '@types/node': specifier: 22.8.1 version: 22.8.1 @@ -78,6 +81,9 @@ importers: eslint-plugin-vue: specifier: 9.29.1 version: 9.29.1(eslint@9.13.0) + html-minifier-terser: + specifier: 7.2.0 + version: 7.2.0 prettier: specifier: 3.3.3 version: 3.3.3 @@ -1306,6 +1312,9 @@ packages: '@types/estree@1.0.6': resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + '@types/html-minifier-terser@7.0.2': + resolution: {integrity: sha512-mm2HqV22l8lFQh4r2oSsOEVea+m0qqxEmwpc9kC1p/XzmjLWrReR9D/GRs8Pex2NX/imyEH9c5IU/7tMBQCHOA==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -1577,6 +1586,9 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} + camel-case@4.1.2: + resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==} + caniuse-lite@1.0.30001672: resolution: {integrity: sha512-XhW1vRo1ob6aeK2w3rTohwTPBLse/rvjq+s3RTSBwnlZqoFFjx9cHsShJjAIbLsLjyoacaTxpLZy9v3gg6zypw==} @@ -1592,6 +1604,10 @@ packages: resolution: {integrity: sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==} engines: {node: '>= 14.16.0'} + clean-css@5.3.3: + resolution: {integrity: sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==} + engines: {node: '>= 10.0'} + color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} @@ -1605,6 +1621,10 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + commander@10.0.1: + resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} + engines: {node: '>=14'} + commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -1690,6 +1710,9 @@ packages: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} engines: {node: '>=6.0.0'} + dot-case@3.0.4: + resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} + eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -2078,6 +2101,11 @@ packages: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true + html-minifier-terser@7.2.0: + resolution: {integrity: sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==} + engines: {node: ^14.13.1 || >=16.0.0} + hasBin: true + idb@7.1.1: resolution: {integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==} @@ -2292,6 +2320,9 @@ packages: lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + lower-case@2.0.2: + resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} + lru-cache@10.3.1: resolution: {integrity: sha512-9/8QXrtbGeMB6LxwQd4x1tIMnsmUxMvIH/qWGsccz6bt9Uln3S+sgAaqfQNhbGA8ufzs2fHuP/yqapGgP9Hh2g==} engines: {node: '>=18'} @@ -2349,6 +2380,9 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + no-case@3.0.4: + resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} + node-addon-api@7.1.1: resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} @@ -2393,6 +2427,9 @@ packages: papaparse@5.4.1: resolution: {integrity: sha512-HipMsgJkZu8br23pW15uvo6sib6wne/4woLZPlFf3rpDyMe9ywEXUsuD7+6K9PRkJlVT51j/sCOYDKGGS3ZJrw==} + param-case@3.0.4: + resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==} + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -2400,6 +2437,9 @@ packages: parse5@7.1.2: resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} + pascal-case@3.1.2: + resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} + path-browserify@1.0.1: resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} @@ -2504,6 +2544,10 @@ packages: resolution: {integrity: sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==} hasBin: true + relateurl@0.2.7: + resolution: {integrity: sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==} + engines: {node: '>= 0.10'} + require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} @@ -4318,6 +4362,8 @@ snapshots: '@types/estree@1.0.6': {} + '@types/html-minifier-terser@7.0.2': {} + '@types/json-schema@7.0.15': {} '@types/node@22.8.1': @@ -4660,6 +4706,11 @@ snapshots: callsites@3.1.0: {} + camel-case@4.1.2: + dependencies: + pascal-case: 3.1.2 + tslib: 2.8.0 + caniuse-lite@1.0.30001672: {} chalk@2.4.2: @@ -4677,6 +4728,10 @@ snapshots: dependencies: readdirp: 4.0.2 + clean-css@5.3.3: + dependencies: + source-map: 0.6.1 + color-convert@1.9.3: dependencies: color-name: 1.1.3 @@ -4689,6 +4744,8 @@ snapshots: color-name@1.1.4: {} + commander@10.0.1: {} + commander@2.20.3: {} common-tags@1.8.2: {} @@ -4748,6 +4805,11 @@ snapshots: dependencies: esutils: 2.0.3 + dot-case@3.0.4: + dependencies: + no-case: 3.0.4 + tslib: 2.8.0 + eastasianwidth@0.2.0: {} echarts@5.5.1: @@ -5218,6 +5280,16 @@ snapshots: he@1.2.0: {} + html-minifier-terser@7.2.0: + dependencies: + camel-case: 4.1.2 + clean-css: 5.3.3 + commander: 10.0.1 + entities: 4.5.0 + param-case: 3.0.4 + relateurl: 0.2.7 + terser: 5.18.2 + idb@7.1.1: {} ignore@5.2.4: {} @@ -5409,6 +5481,10 @@ snapshots: lodash@4.17.21: {} + lower-case@2.0.2: + dependencies: + tslib: 2.8.0 + lru-cache@10.3.1: {} lru-cache@5.1.1: @@ -5458,6 +5534,11 @@ snapshots: natural-compare@1.4.0: {} + no-case@3.0.4: + dependencies: + lower-case: 2.0.2 + tslib: 2.8.0 + node-addon-api@7.1.1: {} node-releases@2.0.12: {} @@ -5504,6 +5585,11 @@ snapshots: papaparse@5.4.1: {} + param-case@3.0.4: + dependencies: + dot-case: 3.0.4 + tslib: 2.8.0 + parent-module@1.0.1: dependencies: callsites: 3.1.0 @@ -5512,6 +5598,11 @@ snapshots: dependencies: entities: 4.5.0 + pascal-case@3.1.2: + dependencies: + no-case: 3.0.4 + tslib: 2.8.0 + path-browserify@1.0.1: {} path-exists@4.0.0: {} @@ -5599,6 +5690,8 @@ snapshots: dependencies: jsesc: 0.5.0 + relateurl@0.2.7: {} + require-from-string@2.0.2: {} resolve-from@4.0.0: {} diff --git a/vite.config.ts b/vite.config.ts index 9f9fbc08..6cfc5a2f 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -3,6 +3,8 @@ import { defineConfig } from 'vite'; import { optimizeCssModules } from 'vite-plugin-optimize-css-modules'; import { VitePWA } from 'vite-plugin-pwa'; import tsconfigPaths from 'vite-tsconfig-paths'; +import { minifyJsonPlugin } from './plugins/vite-plugin-minify-json/minifyJsonPlugin.ts'; +import { minifyHtmlPlugin } from './plugins/vite-plugin-minify-html/minifyHtmlPlugin.ts'; export default defineConfig({ envPrefix: ['OCULAR'], @@ -36,6 +38,8 @@ export default defineConfig({ plugins: [ tsconfigPaths({ loose: true }), optimizeCssModules(), + minifyJsonPlugin(), + minifyHtmlPlugin(), vue({ script: { defineModel: true