diff --git a/.prettierignore b/.prettierignore index 80ef696..cfb9e4c 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,3 +1,3 @@ node_modules/ dist/ -src/templates/*.out.js +src/templates/*.out.json diff --git a/COLLABORATOR_GUIDE.md b/COLLABORATOR_GUIDE.md index cb8bd13..b25e305 100644 --- a/COLLABORATOR_GUIDE.md +++ b/COLLABORATOR_GUIDE.md @@ -55,7 +55,7 @@ The Node.js Release Worker is built upon [Cloudflare Workers](https://developers The Worker also uses several other Open Source libraries (not limited to) listed below: - [AWS S3 client](https://www.npmjs.com/package/@aws-sdk/client-s3) is used for interacting with R2's S3 entrypoint. -- [Handlebars.js](https://www.npmjs.com/package/handlebars) is used for rendering templated pages. +- [Mustache.js](https://www.npmjs.com/package/mustache) is used for rendering templated pages. - [Sentry](https://sentry.io/about) is used for error reporting. ## Code Editing @@ -110,7 +110,7 @@ The bucket's file structure is a 1:1 mapping to the file structure of the releas ### Frontend Bits -The directory listing page is the only frontend page that the Worker serves non-statically. For simplicitly, it is a pre-compiled Handlebars template. +The directory listing page is the only frontend page that the Worker serves non-statically. For simplicitly, it is a pre-compiled Mustache template. Should any other pages be added to this worker, they should do the same unless a consensus is reached by the Collaborators. ## Additional Clarification diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7daaac4..cc567bd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -117,7 +117,7 @@ This repository contains a few scripts and commands for performing numerous task - `node --run lint` Lints the code to the repository's standards. - `node --run test:unit` Runs the [Unit Tests](./COLLABORATOR_GUIDE.md#unit-tests) to ensure individual components are working as expected. - `node --run test:e2e` Runs the [E2E Tests](./COLLABORATOR_GUIDE.md#e2e-tests) to ensure requests act as expected. -- `node --run build:handlebars` Compiles the Handlebars templates. **Required for any changes to the templates to take affect**. +- `node --run build:mustache` Compiles the Mustache templates. **Required for any changes to the templates to take affect**. diff --git a/eslint.config.mjs b/eslint.config.mjs index c108f1f..f4391d7 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -16,7 +16,7 @@ const compat = new FlatCompat({ }); export default [ - { ignores: ['**/node_modules/', '**/dist/', 'src/templates/*.out.js'] }, + { ignores: ['**/node_modules/', '**/dist/'] }, ...compat.extends('eslint:recommended', 'prettier'), { languageOptions: { diff --git a/package-lock.json b/package-lock.json index c1e39fd..8264a40 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,8 @@ "version": "0.0.0", "dependencies": { "@aws-sdk/client-s3": "^3.826.0", - "handlebars": "^4.7.8", "itty-router": "^5.0.18", + "mustache": "^4.2.0", "toucan-js": "^4.1.1" }, "devDependencies": { @@ -18,6 +18,7 @@ "@eslint/eslintrc": "^3.2.0", "@eslint/js": "^9.28.0", "@reporters/github": "^1.7.2", + "@types/mustache": "^4.2.6", "@types/node": "^24.0.1", "@typescript-eslint/eslint-plugin": "^8.33.1", "@typescript-eslint/parser": "^8.32.1", @@ -26,9 +27,9 @@ "eslint-plugin-prettier": "^5.4.1", "glob": "^11.0.2", "globals": "^16.2.0", + "html-minifier-terser": "^7.2.0", "nodejs-latest-linker": "^1.8.0", "prettier": "^3.5.3", - "terser": "^5.42.0", "tsx": "^4.19.4", "typescript": "^5.8.3", "wrangler": "^4.19.1" @@ -3023,6 +3024,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/mustache": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@types/mustache/-/mustache-4.2.6.tgz", + "integrity": "sha512-t+8/QWTAhOFlrF1IVZqKnMRJi84EgkIK5Kh0p2JV4OLywUvCwJPFxbJAl7XAow7DVIHsF+xW9f1MVzg0L6Szjw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "24.0.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.1.tgz", @@ -3446,6 +3454,17 @@ "node": ">=6" } }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -3462,6 +3481,19 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/clean-css": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", + "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 10.0" + } + }, "node_modules/color": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", @@ -3589,6 +3621,17 @@ "node": ">=8" } }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -3601,6 +3644,19 @@ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/esbuild": { "version": "0.25.4", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz", @@ -4193,33 +4249,45 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, - "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/html-minifier-terser": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz", + "integrity": "sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==", + "dev": true, + "license": "MIT", "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" + "camel-case": "^4.1.2", + "clean-css": "~5.3.2", + "commander": "^10.0.0", + "entities": "^4.4.0", + "param-case": "^3.0.4", + "relateurl": "^0.2.7", + "terser": "^5.15.1" }, "bin": { - "handlebars": "bin/handlebars" + "html-minifier-terser": "cli.js" }, "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" + "node": "^14.13.1 || >=16.0.0" } }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/html-minifier-terser/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=14" } }, "node_modules/ignore": { @@ -4403,6 +4471,16 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, "node_modules/lru-cache": { "version": "11.0.2", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.2.tgz", @@ -4488,14 +4566,6 @@ "node": "*" } }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/minipass": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", @@ -4516,7 +4586,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", - "dev": true, "license": "MIT", "bin": { "mustache": "bin/mustache" @@ -4528,10 +4597,16 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } }, "node_modules/nodejs-latest-linker": { "version": "1.8.0", @@ -4606,6 +4681,17 @@ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "dev": true }, + "node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -4618,6 +4704,17 @@ "node": ">=6" } }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -4753,6 +4850,16 @@ ], "license": "MIT" }, + "node_modules/relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -4895,6 +5002,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -5218,18 +5326,6 @@ "dev": true, "license": "MIT" }, - "node_modules/uglify-js": { - "version": "3.17.4", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", - "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", - "optional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/undici": { "version": "5.29.0", "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", @@ -5297,11 +5393,6 @@ "node": ">= 8" } }, - "node_modules/wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==" - }, "node_modules/workerd": { "version": "1.20250525.0", "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20250525.0.tgz", diff --git a/package.json b/package.json index cb3abeb..ceea61c 100644 --- a/package.json +++ b/package.json @@ -11,13 +11,14 @@ "test": "node --run test:unit && node --run test:e2e", "test:unit": "node --test --test-reporter=@reporters/github --test-reporter-destination=stdout --test-reporter=spec --test-reporter-destination=stdout --import=tsx ./tests/unit/index.test.ts", "test:e2e": "wrangler deploy --dry-run --outdir=dist && node --test --test-reporter=@reporters/github --test-reporter-destination=stdout --test-reporter=spec --test-reporter-destination=stdout --import=tsx ./tests/e2e/index.test.ts", - "build:handlebars": "node scripts/compile-handlebars.js" + "build:mustache": "node scripts/compile-mustache.mjs" }, "devDependencies": { "@cloudflare/workers-types": "^4.20250525.0", "@eslint/eslintrc": "^3.2.0", "@eslint/js": "^9.28.0", "@reporters/github": "^1.7.2", + "@types/mustache": "^4.2.6", "@types/node": "^24.0.1", "@typescript-eslint/eslint-plugin": "^8.33.1", "@typescript-eslint/parser": "^8.32.1", @@ -26,17 +27,17 @@ "eslint-plugin-prettier": "^5.4.1", "glob": "^11.0.2", "globals": "^16.2.0", + "html-minifier-terser": "^7.2.0", "nodejs-latest-linker": "^1.8.0", "prettier": "^3.5.3", - "terser": "^5.42.0", "tsx": "^4.19.4", "typescript": "^5.8.3", "wrangler": "^4.19.1" }, "dependencies": { "@aws-sdk/client-s3": "^3.826.0", - "handlebars": "^4.7.8", "itty-router": "^5.0.18", + "mustache": "^4.2.0", "toucan-js": "^4.1.1" } } diff --git a/scripts/compile-handlebars.mjs b/scripts/compile-handlebars.mjs deleted file mode 100644 index 7758efc..0000000 --- a/scripts/compile-handlebars.mjs +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env node - -import { glob } from 'glob'; -import { minify } from 'terser'; -import { join } from 'node:path'; -import { fileURLToPath } from 'node:url'; -import { readFileSync, writeFileSync } from 'node:fs'; -import Handlebars from 'handlebars'; - -const __dirname = fileURLToPath(new URL('.', import.meta.url)); - -const removeStringIndents = str => - // This function allows removing all spaces before a < and - // all new lines and new lines accompanied by spaces - // This is the most simplistic yet effective way to remove - // empty spaces and new lines to minify the HTML result for the Response Body - str.replace(/[ ]+ { - files.forEach(filename => { - const filePath = join(__dirname, '..', filename); - - // Add here because prettier removes it for whatever reason - const sourceTemplate = '' + readFileSync(filePath, 'utf8'); - - const compiledTemplate = Handlebars.precompile(sourceTemplate, { - knownHelpersOnly: true, - preventIndent: true, - noEscape: true, - strict: true, - }); - - // The Handlebars.precompile returns a JavaScript object - // and then we make a default export of the object - const javascriptTemplate = `export default ${compiledTemplate}`; - - const outputFilename = filePath.replace('.hbs', '.out.js'); - - // We minify the resulting JavaScript file with Terser and - // then write on the same directory but with `.out.js` extension - minify(javascriptTemplate).then(({ code }) => - writeFileSync(outputFilename, code + '\n', 'utf8') - ); - }); -}); diff --git a/scripts/compile-mustache.mjs b/scripts/compile-mustache.mjs new file mode 100644 index 0000000..1d701d5 --- /dev/null +++ b/scripts/compile-mustache.mjs @@ -0,0 +1,27 @@ +#!/usr/bin/env node + +import { glob } from 'glob'; +import { join, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { readFileSync, writeFileSync } from 'node:fs'; +import mustache from 'mustache'; +import { minify } from 'html-minifier-terser'; + +const root = join(dirname(fileURLToPath(import.meta.url)), '..'); + +const files = await glob(join(root, 'src', 'templates', '*.html'), { + cwd: root, +}); + +await Promise.all( + files.map(async file => { + const rawHtml = readFileSync(file, 'utf8'); + const minHtml = await minify(rawHtml, { + collapseWhitespace: true, + minifyCSS: true, + }); + const parsed = mustache.parse(minHtml); + const outFile = file.replace(/\.html$/, '.out.json'); + writeFileSync(outFile, JSON.stringify(parsed) + '\n'); + }) +); diff --git a/src/templates/directoryListing.hbs b/src/templates/directoryListing.hbs deleted file mode 100644 index 4871452..0000000 --- a/src/templates/directoryListing.hbs +++ /dev/null @@ -1,25 +0,0 @@ - - - Index of {{pathname}} - - - -

Index of {{pathname}}


../
-{{#each entries}}
-{{name}}{{displayNamePaddingRight}} {{lastModified}} {{size}}
-{{/each}}
-

- diff --git a/src/templates/directoryListing.html b/src/templates/directoryListing.html new file mode 100644 index 0000000..1e05391 --- /dev/null +++ b/src/templates/directoryListing.html @@ -0,0 +1,36 @@ + + + + + Index of {{{pathname}}} + + + + +

Index of {{{pathname}}}

+
+
+../
+{{#entries}}
+{{{name}}}{{{displayNamePaddingRight}}} {{{lastModified}}} {{{size}}}
+{{/entries}}
+
+
+ + + diff --git a/src/templates/directoryListing.out.js b/src/templates/directoryListing.out.js deleted file mode 100644 index bb46f69..0000000 --- a/src/templates/directoryListing.out.js +++ /dev/null @@ -1 +0,0 @@ -export default{1:function(n,l,e,r,t){var o,a=n.strict,i=n.lambda;return''+(null!=(o=i(a(l,"name",{start:{line:22,column:21},end:{line:22,column:25}}),l))?o:"")+""+(null!=(o=i(a(l,"displayNamePaddingRight",{start:{line:22,column:33},end:{line:22,column:56}}),l))?o:"")+" "+(null!=(o=i(a(l,"lastModified",{start:{line:22,column:61},end:{line:22,column:73}}),l))?o:"")+" "+(null!=(o=i(a(l,"size",{start:{line:22,column:78},end:{line:22,column:82}}),l))?o:"")+"\r\n"},compiler:[8,">= 4.3.0"],main:function(n,l,e,r,t){var o,a=n.strict,i=n.lambda,c=n.lookupProperty||function(n,l){if(Object.prototype.hasOwnProperty.call(n,l))return n[l]};return"\r\n\r\n Index of "+(null!=(o=i(a(l,"pathname",{start:{line:3,column:20},end:{line:3,column:28}}),l))?o:"")+"\r\n \r\n\r\n\r\n

Index of "+(null!=(o=i(a(l,"pathname",{start:{line:20,column:15},end:{line:20,column:23}}),l))?o:"")+'


../\r\n'+(null!=(o=c(e,"each").call(null!=l?l:n.nullContext||{},c(l,"entries"),{name:"each",hash:{},fn:n.program(1,t,0),inverse:n.noop,data:t,loc:{start:{line:21,column:0},end:{line:23,column:9}}}))?o:"")+"

\r\n\r\n"},useData:!0}; diff --git a/src/templates/directoryListing.out.json b/src/templates/directoryListing.out.json new file mode 100644 index 0000000..ed5d900 --- /dev/null +++ b/src/templates/directoryListing.out.json @@ -0,0 +1 @@ +[["text","Index of ",0,43],["&","pathname",43,57],["text","

Index of ",57,223],["&","pathname",223,237],["text","


\n../\n",237,274],["#","entries",274,286,[["text","",306,308],["&","name",308,318],["text","",318,322],["&","displayNamePaddingRight",322,351],["text"," ",351,352],["&","lastModified",352,370],["text"," ",370,371],["&","size",371,381],["text","\n",381,382]],382],["text","

",395,419]] diff --git a/src/utils/directoryListing.ts b/src/utils/directoryListing.ts index ff45385..e7d0e11 100644 --- a/src/utils/directoryListing.ts +++ b/src/utils/directoryListing.ts @@ -1,9 +1,7 @@ -import Handlebars from 'handlebars'; import type { File, ReadDirectoryResult } from '../providers/provider'; -import htmlTemplate from '../templates/directoryListing.out'; +import htmlTemplate from '../templates/directoryListing.out.json' with { type: 'json' }; import { toReadableBytes } from '../utils/object'; - -const handlebarsTemplate = Handlebars.template(htmlTemplate); +import { template } from './template'; // Closest we can get to nginx's time format with this api const dateFormatter = new Intl.DateTimeFormat('en-GB', { @@ -33,7 +31,7 @@ export function renderDirectoryListing( tableElements.push(renderFile(url.pathname, file)); } - return handlebarsTemplate({ + return template(htmlTemplate, { pathname: url.pathname, entries: tableElements, }); diff --git a/src/utils/template.ts b/src/utils/template.ts new file mode 100644 index 0000000..ec206d7 --- /dev/null +++ b/src/utils/template.ts @@ -0,0 +1,9 @@ +import mustache from 'mustache'; + +const writer = new mustache.Writer(); + +export const template = ( + tokens: unknown, + view: Record +): string => + writer.renderTokens(tokens as string[][], new mustache.Context(view)); diff --git a/tests/e2e/test-data/expected-html/dist.txt b/tests/e2e/test-data/expected-html/dist.txt index 43a623e..b3786e0 100644 --- a/tests/e2e/test-data/expected-html/dist.txt +++ b/tests/e2e/test-data/expected-html/dist.txt @@ -1,24 +1,5 @@ - - - Index of /dist/v1.0.0/ - - - -

Index of /dist/v1.0.0/


../
+Index of /dist/v1.0.0/

Index of /dist/v1.0.0/


+../
 latest/                                                           -                   -
 index.json                                         12 Sept 2023, 05:43                 18 B
-

- +

\ No newline at end of file