@rhinostone/swig 2.0.1 → 2.1.0

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.
@@ -0,0 +1,8 @@
1
+ [2.1.0](https://github.com/gina-io/swig/tree/v2.1.0) / 2026-05-10
2
+ -----------------------------------------------------------------
3
+
4
+ * **Added** `@rhinostone/swig` and `@rhinostone/swig-twig` gain `renderFileAsync(path, locals, cb)` and `compileFileAsync(path, options, cb)` for use with async-only loaders (S3, Redis, CDN, fetch-backed). The implementation pre-walks the template dependency graph through the user loader's cb-shape arm (`loader.load(path, cb)`), builds an in-memory map keyed by resolved path, then runs the existing sync render path against an in-memory wrapper for the duration of the call. The pre-walker text-scans for `extends` / `include` / `import` (and Twig `from`) string-literal targets only — dynamic paths (`{% extends parent_var %}`) are not pre-resolved and surface a `Pre-walked map missing path` error at render time, with full async-parse support for dynamic paths tracked separately. Isolation strategy is transactional `self.options.loader` mutation for the duration of the sync render — safe under JS event-loop semantics because sync render never yields, so concurrent `renderFileAsync` calls don't interleave their render phases; documented caveat is not to externally mutate `swig.options.loader` mid-flight on the same instance while async renders are pending. Pre-walker modules live at `lib/async/pre-walker.js` (native) and `packages/swig-twig/lib/async/pre-walker.js` (Twig) — per-flavor copies because the rawTag (`raw` vs `verbatim`) and keyword set differ. Existing sync `renderFile` / `compileFile` consumers are unaffected.
5
+
6
+ * **Changed** Internal scaffolding for a future async parse path. `@rhinostone/swig-core` gains four deferred-resolution IR shapes (`IRExtendsDeferred`, `IRIncludeDeferred`, `IRImportDeferred`, `IRFromImportDeferred`) and matching backend emit branches that produce `AsyncFunction`-wrapped template bodies. The async emit flows through a new `_swig.getTemplate(<path>, options)` runtime helper that returns `Promise<TemplateFn>`, and async-mode template functions return `Promise<{output, exports}>` so importers can pick up macro definitions at runtime. Block override resolution rides on a new sixth `_blocks` positional parameter on the wrapped AsyncFunction. All gated behind an explicit `options.codegenMode === 'async'` flag — default is sync and no behavior changes for existing consumers; the frontend tag wiring and public API dispatch that would activate this path are not yet shipped. Mock coverage at `tests/swig-core/async-emit.test.js` (63 cases across the four deferred shapes plus macro exports and block override). ESLint `parserOptions.ecmaVersion` bumped to 2017 for the AsyncFunction literal.
7
+
8
+ * **Changed** Internal cleanup. `@rhinostone/swig-core` `IRVarRef` emit shape simplified from a nested-ternary dot-walk into a single-evaluation ternary expression with the same runtime semantics, producing smaller compiled bodies and easier-to-read `precompile().tpl.toString()` output. The legacy twin in `lib/tokenparser.js`'s `checkMatch` updated to match. Phase 2 byte-identity test rewritten to assert the new shape; multi-segment substring assertions still hold unchanged. New `benchmarks/` directory with a runnable render-throughput suite comparing `@rhinostone/swig` against Nunjucks (`benchmarks/render.js`), excluded from the published tarball via `.npmignore`. README clarifications surfaced the prototype-pollution hardening across CVE-2023-25345 / CVE-2021-44906 and added a benchmark pointer.
package/.eslintrc.json CHANGED
@@ -1,7 +1,14 @@
1
1
  {
2
2
  "env": {
3
3
  "node": true,
4
- "mocha": true
4
+ "mocha": true,
5
+ "es2017": true
6
+ },
7
+ "parserOptions": {
8
+ "ecmaVersion": 2017
9
+ },
10
+ "globals": {
11
+ "Promise": "readonly"
5
12
  },
6
13
  "rules": {
7
14
  "max-len": ["error", { "code": 600 }],
package/HISTORY.md CHANGED
@@ -1,3 +1,12 @@
1
+ [2.1.0](https://github.com/gina-io/swig/tree/v2.1.0) / 2026-05-10
2
+ -----------------------------------------------------------------
3
+
4
+ * **Added** `@rhinostone/swig` and `@rhinostone/swig-twig` gain `renderFileAsync(path, locals, cb)` and `compileFileAsync(path, options, cb)` for use with async-only loaders (S3, Redis, CDN, fetch-backed). The implementation pre-walks the template dependency graph through the user loader's cb-shape arm (`loader.load(path, cb)`), builds an in-memory map keyed by resolved path, then runs the existing sync render path against an in-memory wrapper for the duration of the call. The pre-walker text-scans for `extends` / `include` / `import` (and Twig `from`) string-literal targets only — dynamic paths (`{% extends parent_var %}`) are not pre-resolved and surface a `Pre-walked map missing path` error at render time, with full async-parse support for dynamic paths tracked separately. Isolation strategy is transactional `self.options.loader` mutation for the duration of the sync render — safe under JS event-loop semantics because sync render never yields, so concurrent `renderFileAsync` calls don't interleave their render phases; documented caveat is not to externally mutate `swig.options.loader` mid-flight on the same instance while async renders are pending. Pre-walker modules live at `lib/async/pre-walker.js` (native) and `packages/swig-twig/lib/async/pre-walker.js` (Twig) — per-flavor copies because the rawTag (`raw` vs `verbatim`) and keyword set differ. Existing sync `renderFile` / `compileFile` consumers are unaffected.
5
+
6
+ * **Changed** Internal scaffolding for a future async parse path. `@rhinostone/swig-core` gains four deferred-resolution IR shapes (`IRExtendsDeferred`, `IRIncludeDeferred`, `IRImportDeferred`, `IRFromImportDeferred`) and matching backend emit branches that produce `AsyncFunction`-wrapped template bodies. The async emit flows through a new `_swig.getTemplate(<path>, options)` runtime helper that returns `Promise<TemplateFn>`, and async-mode template functions return `Promise<{output, exports}>` so importers can pick up macro definitions at runtime. Block override resolution rides on a new sixth `_blocks` positional parameter on the wrapped AsyncFunction. All gated behind an explicit `options.codegenMode === 'async'` flag — default is sync and no behavior changes for existing consumers; the frontend tag wiring and public API dispatch that would activate this path are not yet shipped. Mock coverage at `tests/swig-core/async-emit.test.js` (63 cases across the four deferred shapes plus macro exports and block override). ESLint `parserOptions.ecmaVersion` bumped to 2017 for the AsyncFunction literal.
7
+
8
+ * **Changed** Internal cleanup. `@rhinostone/swig-core` `IRVarRef` emit shape simplified from a nested-ternary dot-walk into a single-evaluation ternary expression with the same runtime semantics, producing smaller compiled bodies and easier-to-read `precompile().tpl.toString()` output. The legacy twin in `lib/tokenparser.js`'s `checkMatch` updated to match. Phase 2 byte-identity test rewritten to assert the new shape; multi-segment substring assertions still hold unchanged. New `benchmarks/` directory with a runnable render-throughput suite comparing `@rhinostone/swig` against Nunjucks (`benchmarks/render.js`), excluded from the published tarball via `.npmignore`. README clarifications surfaced the prototype-pollution hardening across CVE-2023-25345 / CVE-2021-44906 and added a benchmark pointer.
9
+
1
10
  [2.0.1](https://github.com/gina-io/swig/tree/v2.0.1) / 2026-05-10
2
11
  -----------------------------------------------------------------
3
12
 
package/README.md CHANGED
@@ -29,11 +29,23 @@ Features
29
29
  * [Express](http://expressjs.com/) compatible.
30
30
  * Object-Oriented template inheritance.
31
31
  * Apply filters and transformations to output in your templates.
32
- * Automatically escapes all output for safe HTML rendering.
32
+ * **Hardened against prototype-pollution** `__proto__` / `constructor` / `prototype` blocked at parser, tag-side, and IR-emission layers. CVE-2023-25345 fully patched. 9 regression cases under [`tests/regressions.test.js`](./tests/regressions.test.js).
33
+ * Automatically escapes all variable output (HTML by default; configurable per-call).
33
34
  * Lots of iteration and conditionals supported.
34
35
  * Robust without the bloat.
35
36
  * Extendable and customizable — register custom filters, tags, and loaders per-instance.
36
37
 
38
+ Benchmarks
39
+ ----------
40
+
41
+ [`benchmarks/render.js`](./benchmarks/render.js) measures sync-render throughput across five workload shapes against [Nunjucks](https://www.npmjs.com/package/nunjucks).
42
+
43
+ ```bash
44
+ cd benchmarks && npm install && node render.js
45
+ ```
46
+
47
+ In production-typical settings (autoescape on), `@rhinostone/swig` outperforms Nunjucks on iteration-heavy templates by 2–3.5× and ties on simple control flow. See [`benchmarks/README.md`](./benchmarks/README.md) for the methodology, the full result table, and how to reproduce on your own hardware.
48
+
37
49
  Need Help? Have Questions? Comments?
38
50
  ------------------------------------
39
51
 
package/ROADMAP.md CHANGED
@@ -21,6 +21,12 @@ _No near-term scheduled items. See [Future (post-2.0)](#future-post-20) for upco
21
21
 
22
22
  ## Completed
23
23
 
24
+ ### v2.1.0 (May 2026)
25
+
26
+ - Async loader support via `renderFileAsync(path, locals, cb)` and `compileFileAsync(path, options, cb)` on `@rhinostone/swig` and `@rhinostone/swig-twig`. The implementation pre-walks the template dependency graph through the user loader's cb-shape arm, builds an in-memory map keyed by resolved path, then runs the existing sync render against an in-memory wrapper for the duration of the call. Supports `extends`, `include`, `import`, and Twig `from import` with string-literal paths; dynamic paths surface a `Pre-walked map missing path` error at render time. Existing sync `renderFile` / `compileFile` consumers are unaffected.
27
+ - Internal scaffolding for a future async parse path: new deferred-resolution IR shapes (`IRExtendsDeferred`, `IRIncludeDeferred`, `IRImportDeferred`, `IRFromImportDeferred`) and matching `AsyncFunction`-wrapped backend emit branches land on `@rhinostone/swig-core`, all gated behind `options.codegenMode === 'async'` (default sync, no behavior change for existing consumers). The frontend tag wiring and public API dispatch that would activate this path are not yet shipped.
28
+ - Internal cleanup: simplified `IRVarRef` emit shape into a single-evaluation ternary (smaller compiled bodies, same runtime semantics); added a runnable render-throughput benchmark suite at `benchmarks/render.js` (excluded from the published tarball); README clarifications surfaced the prototype-pollution hardening across CVE-2023-25345 and CVE-2021-44906.
29
+
24
30
  ### v2.0.1 (May 2026)
25
31
 
26
32
  - Fixed bracket-access expressions failing on unspaced binary arithmetic in `@rhinostone/swig` and `@rhinostone/swig-twig`. The lexer's NUMBER rule was greedy-eating leading `+` / `-` operators inside bracket expressions like `arr[arr.length-1]` and `arr[idx-1]`, causing the parser to bail with "Unexpected closing square bracket". Dropped the optional sign prefix from the NUMBER rule; signed-literal paths continue to work via the existing unary-operator wrapping.