vanilla-jet 1.5.5 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,42 @@ All notable project changes are documented in this file.
4
4
 
5
5
  The format follows a structure inspired by Keep a Changelog and semantic versioning.
6
6
 
7
+ ## [1.6.1] - 2026-06-29
8
+
9
+ ### Security
10
+
11
+ - **Removed unused dependencies that carried known vulnerabilities:** `jsrsasign` (multiple high/critical
12
+ crypto advisories), `jwt-simple`, `blueimp-md5`, `js-beautify`, and `nodemon`. None were used by the
13
+ framework; this drops a large vulnerability + install-size footprint for consumers.
14
+
15
+ ### Added
16
+
17
+ - **`settings.profile.static_cache_max_age`** (seconds, default `0`): sets `Cache-Control: public, max-age=N`
18
+ for NON-versioned static assets (images, fonts, animation JSON…), so they are reused across references
19
+ and reloads instead of revalidating every time. Versioned (`?v=`) assets stay `immutable`; `0` keeps the
20
+ previous `no-cache` behavior. Pairs with the service worker for clients without it (e.g. WebViews).
21
+
22
+ ## [1.6.0] - 2026-06-29
23
+
24
+ ### Added
25
+
26
+ - **Template externalization (opt-in):** `settings.profile.externalize_templates`. `compile_html.js`
27
+ moves `<script type="text/template">` blocks out of the page into a cacheable `public/scripts/templates.js`
28
+ (loaded `defer` before the app bundle, so views still find their templates in the DOM at boot). Shrinks
29
+ the initial HTML dramatically; the templates file is brotli + immutable + service-worker cached. The SW
30
+ precache now includes `templates.js`.
31
+
32
+ ### Fixed
33
+
34
+ - **Watch keeps precompressed assets fresh:** `compressBr` now runs in the LESS/JS watch series, so `.gz`/`.br`
35
+ no longer go stale on incremental dev rebuilds.
36
+
37
+ ### Notes
38
+
39
+ - Recommended: enable caching features (`enable_service_worker`, `externalize_templates`, precompression,
40
+ immutable) in `qa`/`production`; keep `enable_service_worker` **off in development` so a cache-first SW does
41
+ not serve stale assets during rapid rebuilds.
42
+
7
43
  ## [1.5.5] - 2026-06-28
8
44
 
9
45
  ### Changed
package/README.md CHANGED
@@ -1,19 +1,23 @@
1
1
  # VanillaJet
2
2
 
3
- Node.js framework for building SPA applications with a JS/CSS/HTML build pipeline, HTTP/HTTPS server, internal router, and template rendering utilities.
3
+ Node.js framework for building SPA applications: a Gulp build pipeline (JS/CSS/HTML), a lightweight
4
+ HTTP/HTTPS server, an internal router, and template/resource utilities — with first-class, opt-in
5
+ performance features (Brotli, immutable caching, a generated service worker, deferred scripts, and
6
+ template externalization).
4
7
 
5
8
  ![VanillaJet logo](https://github.com/nalancer08/App-Builders/blob/master/Logos/logo_monocromatico_horizontal_.png)
6
9
 
7
10
  ## Current version
8
11
 
9
- - Version: `1.4.3`
10
- - Changelog: see [`CHANGELOG.md`](./CHANGELOG.md)
11
- - Improvement plan (performance and backward compatibility): see `ROADMAP_INTEGRAL.md`
12
+ - Version: `1.6.0`
13
+ - Changelog: [`CHANGELOG.md`](./CHANGELOG.md)
14
+ - **Full project docs (architecture, runtime, build, deployment, perf): [`master.md`](./master.md)**
15
+ - Roadmap: [`ROADMAP_INTEGRAL.md`](./ROADMAP_INTEGRAL.md)
12
16
 
13
17
  ## Requirements
14
18
 
15
- - Node.js `>=16` recommended
16
- - npm `>=8`
19
+ - Node.js `>= 18` (tested on 24)
20
+ - npm `>= 8`
17
21
 
18
22
  ## Installation
19
23
 
@@ -21,124 +25,110 @@ Node.js framework for building SPA applications with a JS/CSS/HTML build pipelin
21
25
  npm install vanilla-jet
22
26
  ```
23
27
 
24
- If you are working in this local repository:
25
-
26
- ```bash
27
- npm install
28
- ```
29
-
30
28
  ## Quick start
31
29
 
32
- ### 1) Export the server from your project
33
-
34
30
  ```js
31
+ // index.js
35
32
  const { Server } = require('vanilla-jet');
36
- ```
37
-
38
- ### 2) Define endpoints (classes)
39
-
40
- Each endpoint should expose a `name` and register routes with the router in the constructor.
33
+ const Config = require('./config');
41
34
 
42
- ```js
43
35
  class AppEndpoint {
44
36
  constructor(router) {
45
37
  this.name = 'AppEndpoint';
46
- router.addRoute('get', '/', 'AppEndpoint.index');
38
+ router.setDefaultRoute('home'); // maps "/"
39
+ router.addRoute('get', '/home', 'AppEndpoint.home');
47
40
  }
48
-
49
- index(request, response) {
50
- response.setBody('Hello VanillaJet');
51
- response.respond();
41
+ home(request, response) {
42
+ response.render(request, 'home.html'); // streams public/pages/home.html
43
+ return true; // <- mark the request as handled
52
44
  }
53
45
  }
54
- ```
55
-
56
- ### 3) Start the server
57
-
58
- ```js
59
- const { Server } = require('vanilla-jet');
60
- const Config = require('./config');
61
46
 
62
47
  new Server(Config, [AppEndpoint]).start();
63
48
  ```
64
49
 
65
- ## Available commands
50
+ ## Configuration (`config.js`)
66
51
 
67
- From this repository:
52
+ Two shapes are supported (both resolved automatically):
68
53
 
69
- - `npm run setup`: generates a base `vanillaJet.package.json` if it does not exist.
70
- - `npm run dev`: build + watcher for development.
71
- - `npm run build:qa`: build for QA.
72
- - `npm run build:staging`: build for staging.
73
- - `npm run build:prod`: build for production.
74
- - `npm run benchmark:static`: runs reproducible static serving benchmark (cold/warm).
54
+ ```js
55
+ // Legacy (keyed by active profile name, selected with `--p qa`)
56
+ module.exports = {
57
+ profile: process.argv... , // 'development' | 'qa' | 'production'
58
+ settings: {
59
+ development: { port: 1234, api_url: '...' },
60
+ qa: { port: 443, api_url: '...' },
61
+ production: { port: 443, api_url: '...' },
62
+ shared: { site_name: 'My App', environment: 'qa', sentry: {...} },
63
+ security: { pass_salt: '...', token_salt: '...' }
64
+ }
65
+ };
75
66
 
76
- As CLI (`bin.js`):
67
+ // Nested (single profile)
68
+ module.exports = { settings: { profile: { port: 8080, api_url: '...' }, shared: {...}, security: {...} } };
69
+ ```
77
70
 
78
- - `npx vanilla-jet setup`
79
- - `npx vanilla-jet dev`
80
- - `npx vanilla-jet build`
71
+ ### Profile options (all optional)
81
72
 
82
- ## Expected consumer project structure
73
+ | Option | Default | What it does |
74
+ |---|---|---|
75
+ | `port` | `8080` | Listen port. `process.env.PORT` wins (Cloud Run/Heroku). |
76
+ | `enable_precompressed_negotiation` | `false` | Serve `.br` → `.gz` → original via `Accept-Encoding`. |
77
+ | `enable_service_worker` | `false` | Generate + serve a cache-first service worker. **Recommended: on in prod/qa, off in dev.** |
78
+ | `service_worker` | — | `{ cache_prefix, on_demand_prefixes, precache, precache_exclude }`. Precache is auto-derived from `vanillaJet.package.json`. |
79
+ | `defer_scripts` | `false` | Add `defer` to non-async scripts so they don't block parsing. |
80
+ | `externalize_templates` | `false` | Move `<script type="text/template">` blocks out of the page into a cacheable `public/scripts/templates.js`. |
81
+ | `request_timeout_ms` / `headers_timeout_ms` / `keep_alive_timeout_ms` | `30000` / `35000` / `5000` | Defensive server timeouts. |
82
+ | `https_server` / `self_managed_certs` | `false` | HTTP/2 with self-managed `key`/`cert`. |
83
83
 
84
- VanillaJet expects a structure similar to:
84
+ ## Commands
85
85
 
86
- - `assets/pages/home.html`
87
- - `assets/templates/**/*.html`
88
- - `assets/scripts/**/*.js`
89
- - `assets/styles/less/admin.less`
90
- - `public/` (compiled output)
91
- - `config.js`
92
- - `vanillaJet.package.json`
86
+ CLI (`bin.js`): `npx vanilla-jet setup | dev | build | build:qa | build:staging | build:prod`
93
87
 
94
- ## Build pipeline (summary)
88
+ From this repo: `npm run dev` · `npm run build:prod` · `npm test` · `npm run benchmark:static`
95
89
 
96
- Gulp-based pipeline (no Grunt):
90
+ ## Expected consumer structure
97
91
 
98
- - Minifies JS and concatenates into `public/scripts/vanilla.min.js`
99
- - Compiles LESS and generates `public/styles/app.min.css`
100
- - Compiles templates and generates `public/pages/home.html`
101
- - Generates `.gz` versions of JS/CSS/HTML for compressed delivery
92
+ ```
93
+ assets/pages/home.html · assets/templates/**/*.html · assets/scripts/**/*.js · assets/styles/less/admin.less
94
+ config.js · vanillaJet.package.json · public/ (build output)
95
+ ```
102
96
 
103
- ## Compression negotiation (optional)
97
+ ## Build pipeline (Gulp)
104
98
 
105
- You can enable precompressed static negotiation from `settings.profile`:
99
+ - Minifies + concatenates JS `public/scripts/vanilla.min.js`
100
+ - Compiles LESS → `public/styles/app.min.css`
101
+ - Compiles templates → `public/pages/home.html` (+ optional `templates.js`)
102
+ - Precompresses every `.js`/`.css`/`.html` to `.gz` + `.br`
103
+ - Generates the service worker (when enabled)
106
104
 
107
- ```js
108
- module.exports = {
109
- settings: {
110
- profile: {
111
- // Enables priority: .br -> .gz -> original file
112
- enable_precompressed_negotiation: true
113
- }
114
- }
115
- };
116
- ```
117
-
118
- Behavior details:
105
+ ## Performance features (opt-in)
119
106
 
120
- - Default (`false`): keeps existing gzip behavior for supported static assets.
121
- - Enabled (`true`): if client accepts Brotli, server tries `.br` first.
122
- - Safe fallback: if `.br` or `.gz` does not exist, server serves the original file.
123
- - HTML rendering (`response.render`) also uses safe runtime fallback for precompressed templates (`.br`/`.gz`/original).
107
+ - **Brotli + gzip** precompression with `Accept-Encoding` negotiation and safe fallback.
108
+ - **Immutable caching**: fingerprinted assets (`?v=size-mtime`) are served `Cache-Control: public,
109
+ max-age=31536000, immutable`; HTML and unversioned assets stay `no-cache`. Big win for clients
110
+ without the service worker (e.g. native WebViews).
111
+ - **Service worker** (cache-first), precache auto-derived from `vanillaJet.package.json`, content-pinned
112
+ cache name, `ignoreSearch` for fingerprinted URLs, and an inline registration helper
113
+ (`dipper.includeServiceWorker()`, web-only with a `window.__VJ_DISABLE_SW__` opt-out for WebViews).
114
+ - **`defer` scripts** and **template externalization** to shrink the render-blocking critical path.
124
115
 
125
- ## Static performance notes (HU 2.1)
116
+ See [`master.md`](./master.md) §12–§17 and [`docs/benchmark-static.md`](./docs/benchmark-static.md).
126
117
 
127
- Static serving includes a warm-path optimization focused on Node runtime latency:
118
+ ## Testing
128
119
 
129
- - Reuses static resolution for repeated requests (`route + accept-encoding`).
130
- - Keeps conditional revalidation (`ETag`/`Last-Modified`) strict so reload reflects changes immediately.
131
- - Keeps streaming strategy for large assets (`fs.createReadStream`) with tuned chunk size.
132
- - Preserves conditional cache behavior (`ETag`/`Last-Modified` + `304`) and precompressed fallback contract.
120
+ ```bash
121
+ npm test # node --test (router, dipper, config, static serving, service worker)
122
+ npm run benchmark:static # reproducible static-serving benchmark (cold/warm)
123
+ ```
133
124
 
134
- Benchmark guide:
125
+ ## Deployment
135
126
 
136
- - [`docs/benchmark-static.md`](./docs/benchmark-static.md)
127
+ Templates (nginx + Docker) in [`docs/deployment/`](./docs/deployment/). Honors `process.env.PORT`, so
128
+ PaaS runtimes (Cloud Run, Heroku) work without config changes. Enable caching features in prod/qa
129
+ profiles; keep them off in `development` for fresh iteration.
137
130
 
138
- ## Additional documentation
131
+ ## More docs
139
132
 
140
- - Router: `docs/router.md`
141
- - Benchmark: [`docs/benchmark-static.md`](./docs/benchmark-static.md)
142
- - Version history: [`CHANGELOG.md`](./CHANGELOG.md)
143
- - Roadmap and improvements: `ROADMAP_INTEGRAL.md`
144
- - Deployment templates (nginx + docker): `docs/deployment/`
133
+ - **[`master.md`](./master.md)** — full architecture, runtime flow, perf playbook, upgrade notes.
134
+ - [`docs/router.md`](./docs/router.md) · [`CHANGELOG.md`](./CHANGELOG.md) · [`ROADMAP_INTEGRAL.md`](./ROADMAP_INTEGRAL.md)
@@ -46,6 +46,10 @@ class Router {
46
46
  this.compressionFiles = [ 'vanilla.min.js', 'app.min.css' ];
47
47
  this.enablePrecompressedNegotiation = Boolean(server?.options?.enable_precompressed_negotiation);
48
48
  this.enableServiceWorker = Boolean(server?.options?.enable_service_worker);
49
+ // Cache-Control max-age (seconds) for NON-versioned static assets (images, fonts,
50
+ // animation JSON…). Lets the browser reuse them across references/reloads instead of
51
+ // revalidating each time. Versioned (?v=) assets stay immutable; 0 keeps no-cache.
52
+ this.staticCacheMaxAge = Number(server?.options?.static_cache_max_age) || 0;
49
53
  }
50
54
 
51
55
  routeToRegExp(route) {
@@ -284,6 +288,7 @@ class Router {
284
288
  }
285
289
 
286
290
  buildStaticHeaders(extHeader, candidates, contentEncoding, metadata, isImmutable) {
291
+ let obj = this;
287
292
  let staticHeaders = Object.assign({}, extHeader);
288
293
  if (contentEncoding) {
289
294
  staticHeaders['Content-Encoding'] = contentEncoding;
@@ -300,8 +305,13 @@ class Router {
300
305
  // This is the big win for clients without the service worker (e.g. native WebViews),
301
306
  // which otherwise revalidate every asset on every load.
302
307
  staticHeaders['Cache-Control'] = 'public, max-age=31536000, immutable';
308
+ } else if (obj.staticCacheMaxAge > 0) {
309
+ // Non-versioned assets (images, fonts, animation JSON…): cache for a while so they
310
+ // aren't re-fetched on every reference/reload. ETag/Last-Modified still allow a cheap
311
+ // 304 after expiry. Pick this when assets change rarely (e.g. a few days/weeks).
312
+ staticHeaders['Cache-Control'] = `public, max-age=${obj.staticCacheMaxAge}`;
303
313
  } else {
304
- // Non-versioned assets: force revalidation to keep clients fresh without a hard reload.
314
+ // Default: force revalidation to keep clients fresh without a hard reload.
305
315
  staticHeaders['Cache-Control'] = 'no-cache, must-revalidate';
306
316
  }
307
317
  return staticHeaders;
@@ -38,6 +38,7 @@ class Server {
38
38
  enable_precompressed_negotiation: false,
39
39
  enable_service_worker: false,
40
40
  defer_scripts: false,
41
+ static_cache_max_age: 0,
41
42
  request_timeout_ms: 30000,
42
43
  headers_timeout_ms: 35000,
43
44
  keep_alive_timeout_ms: 5000
package/gulpfile.js CHANGED
@@ -142,7 +142,8 @@ function watchFiles(cb) {
142
142
  buildLess,
143
143
  compressCss,
144
144
  compileTemplates,
145
- generateServiceWorker
145
+ generateServiceWorker,
146
+ compressBr
146
147
  ));
147
148
 
148
149
  // Watch HTML files
@@ -159,7 +160,8 @@ function watchFiles(cb) {
159
160
  cleanMinified,
160
161
  compressJs,
161
162
  compileTemplates,
162
- generateServiceWorker
163
+ generateServiceWorker,
164
+ compressBr
163
165
  ));
164
166
 
165
167
  cb();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vanilla-jet",
3
- "version": "1.5.5",
3
+ "version": "1.6.1",
4
4
  "description": "VannilaJet framework",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -30,14 +30,9 @@
30
30
  },
31
31
  "homepage": "https://github.com/nalancer08/VanillaJet#readme",
32
32
  "dependencies": {
33
- "blueimp-md5": "2.19.0",
34
33
  "chalk": "4.1.2",
35
34
  "html-minifier-terser": "7.2.0",
36
- "js-beautify": "1.15.4",
37
- "jsrsasign": "11.1.0",
38
- "jwt-simple": "0.5.6",
39
35
  "minimist": "1.2.8",
40
- "nodemon": "3.1.10",
41
36
  "nunjucks": "3.2.4",
42
37
  "underscore": ">= 1.12.x",
43
38
  "del": "^6.0.0",
@@ -184,7 +184,7 @@ function replaceInclude(lines, originalLine, templateCompiled) {
184
184
  // -- Step 4
185
185
  async function createHTMLFile(content, filePath) {
186
186
  const { minify } = require('html-minifier-terser');
187
- const minified = await minify(content, {
187
+ let minified = await minify(content, {
188
188
  collapseWhitespace: true,
189
189
  collapseInlineTagWhitespace: true,
190
190
  removeComments: true,
@@ -195,6 +195,14 @@ async function createHTMLFile(content, filePath) {
195
195
  minifyJS: true
196
196
  });
197
197
 
198
+ // -- Externalize <script type="text/template"> blocks into public/scripts/templates.js
199
+ // (opt-in via settings.profile.externalize_templates). The page shrinks to ~the shell;
200
+ // templates load from a separate cacheable file (brotli + SW + immutable) BEFORE the app
201
+ // bundle (defer + document order), so views still find their templates in the DOM at boot.
202
+ if (opts.externalize_templates) {
203
+ minified = externalizeTemplates(minified);
204
+ }
205
+
198
206
  const publicPath = path.join(processCwd(), '/public/pages');
199
207
  fs.mkdirSync(publicPath, { recursive: true });
200
208
  const absolutePath = path.join(publicPath, filePath);
@@ -210,6 +218,45 @@ async function createHTMLFile(content, filePath) {
210
218
  console.log(chalk.green(`Created gzipped version at: ${absolutePath}.gz`));
211
219
  }
212
220
 
221
+ // -- Move all <script type="text/template"> blocks out of the page into
222
+ // public/scripts/templates.js (a tiny injector that adds them to the DOM). Returns the
223
+ // page HTML with those blocks replaced by a single deferred <script> tag (placed where the
224
+ // first block was — before the app bundle, so it runs first by document order).
225
+ function externalizeTemplates(html) {
226
+ const templateRegex = /<script\b[^>]*\btype\s*=\s*["']text\/template["'][^>]*>[\s\S]*?<\/script>/gi;
227
+ const blocks = html.match(templateRegex) || [];
228
+ if (!blocks.length) {
229
+ return html;
230
+ }
231
+
232
+ const scriptsDir = path.join(processCwd(), 'public', 'scripts');
233
+ fs.mkdirSync(scriptsDir, { recursive: true });
234
+
235
+ const payload = blocks.join('\n');
236
+ // External file → a literal </script> inside the payload is harmless (no inline HTML parser).
237
+ const injector =
238
+ '(function(){var c=document.createElement("div");c.style.display="none";' +
239
+ 'c.setAttribute("data-vj-templates","");c.innerHTML=' + JSON.stringify(payload) + ';' +
240
+ 'document.body.insertBefore(c,document.body.firstChild);})();';
241
+
242
+ const templatesPath = path.join(scriptsDir, 'templates.js');
243
+ fs.writeFileSync(templatesPath, injector, 'utf8');
244
+ fs.writeFileSync(templatesPath + '.gz', zlib.gzipSync(injector, { level: 9 }));
245
+
246
+ const stats = fs.statSync(templatesPath);
247
+ const version = `${stats.size}-${Math.floor(stats.mtimeMs)}`;
248
+ const tag = `<script defer src="/public/scripts/templates.js?v=${version}"></script>`;
249
+
250
+ let inserted = false;
251
+ const result = html.replace(templateRegex, () => {
252
+ if (!inserted) { inserted = true; return tag; }
253
+ return '';
254
+ });
255
+
256
+ console.log(chalk.green(`VanillaJet - externalized ${blocks.length} templates to public/scripts/templates.js`));
257
+ return result;
258
+ }
259
+
213
260
  // -- Helpers
214
261
  function cleanALine(line) {
215
262
  line = line.replaceAll(' ', '');
@@ -15,7 +15,8 @@ const TEMPLATE_PATH = path.join(__dirname, '..', 'framework', 'sw.template.js');
15
15
  const CORE_PRECACHE = [
16
16
  '/public/styles/app.min.css',
17
17
  '/public/scripts/vanilla.min.js',
18
- '/public/scripts/core/vanillaJet.min.js'
18
+ '/public/scripts/core/vanillaJet.min.js',
19
+ '/public/scripts/templates.js'
19
20
  ];
20
21
 
21
22
  const DEFAULT_ON_DEMAND_PREFIXES = ['/public/animations/', '/public/images/'];
@@ -48,6 +48,18 @@ test('isProtectedFile: blocks framework/external/node_modules and top-level file
48
48
  assert.equal(router.isProtectedFile('/public/scripts/vanilla.min.js'), false);
49
49
  });
50
50
 
51
+ test('buildStaticHeaders: immutable for ?v=, max-age when configured, else no-cache', () => {
52
+ const meta = { size: 10, etag: 'W/"x"', lastModified: 'Mon, 01 Jan 2024 00:00:00 GMT' };
53
+ const plain = makeRouter();
54
+ assert.match(plain.buildStaticHeaders({}, [], '', meta, true)['Cache-Control'], /immutable/);
55
+ assert.match(plain.buildStaticHeaders({}, [], '', meta, false)['Cache-Control'], /no-cache/);
56
+
57
+ const cached = new Router({ options: { static_cache_max_age: 3600 } });
58
+ assert.match(cached.buildStaticHeaders({}, [], '', meta, false)['Cache-Control'], /max-age=3600/);
59
+ // Versioned still wins over max-age.
60
+ assert.match(cached.buildStaticHeaders({}, [], '', meta, true)['Cache-Control'], /immutable/);
61
+ });
62
+
51
63
  test('mimes: serves common web fonts and icons', () => {
52
64
  const router = makeRouter();
53
65
  assert.equal(router.mimes['woff2'], 'font/woff2');