@rhinostone/swig 2.1.0 → 2.2.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.
- package/.changes/v2.2.0.md +8 -0
- package/HISTORY.md +9 -0
- package/ROADMAP.md +7 -0
- package/dist/swig.js +120 -41
- package/dist/swig.min.js +2 -2
- package/dist/swig.min.js.map +1 -1
- package/lib/filters.js +23 -20
- package/lib/swig.js +13 -2
- package/lib/tags/import.js +42 -21
- package/lib/tags/include.js +4 -0
- package/package.json +2 -2
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
[2.2.0](https://github.com/gina-io/swig/tree/v2.2.0) / 2026-05-11
|
|
2
|
+
-----------------------------------------------------------------
|
|
3
|
+
|
|
4
|
+
* **Added** `renderFile(path, locals, cb)` and `compileFile(path, options, cb)` automatically route to the async-codegen path when the configured loader signals async support via `loader.async === true`. The async path defers template resolution from parse time to render time via a new `_swig.getTemplate(path, options)` runtime helper that returns `Promise<TemplateFn>` — `extends`, `include`, `import`, and `from` emit deferred IR shapes from the frontend (`IRExtendsDeferred`, `IRIncludeDeferred`, `IRImportDeferred`, `IRFromImportDeferred`) and the shared backend wraps the compiled body in an `AsyncFunction`. Block overrides thread through the inheritance chain via a sixth `_blocks` positional argument on the wrapped template function; macro imports pick up exports via the new `Promise<{output, exports}>` template-fn return shape. Both `@rhinostone/swig` and `@rhinostone/swig-twig` flavors — parity across the two surfaces. Static template targets (string literals in `extends` / `include` / `import` / `from`) work end-to-end against async loaders; dynamic targets (`{% extends parent_var %}`, `{% include user_template %}`) are not yet supported on the async path and surface a clear runtime error with the unresolved expression visible in the message — full dynamic-target support is tracked as a follow-up. The sync render path is unchanged — loaders without `loader.async === true` continue to use the established sync `_swig.compileFile(...)` resolution, including the built-in `loaders.fs` and `loaders.memory` which remain dual-mode. End-to-end coverage at `tests/async/render-file-cb-dispatch.test.js` (native, 11 cases) and `tests/swig-twig/async/render-file-cb-dispatch.test.js` (Twig, 11 cases) covers static extends chains, includes, macro imports, mixed graphs, dynamic include paths surfacing the runtime error, the `ignoreMissing` flag, `with-context` isolation, bare-name and aliased `from` import, and error propagation.
|
|
5
|
+
|
|
6
|
+
* **Changed** `renderFileAsync(path, locals, cb)` and `compileFileAsync(path, options, cb)` on both `@rhinostone/swig` and `@rhinostone/swig-twig` are soft-deprecated via JSDoc only — no runtime warning, no `console.warn`. Use `renderFile(path, locals, cb)` / `compileFile(path, options, cb)` with an async loader (`loader.async === true`) instead; the dispatch is automatic. The legacy pre-walker entry points remain fully functional in 2.x and will be removed in 3.0.
|
|
7
|
+
|
|
8
|
+
* **Changed** Improved the performance of the `escape` / `e` filter in both `@rhinostone/swig` and `@rhinostone/swig-twig`. The HTML default branch now runs a two-pass form — an entity-aware first pass that preserves already-escaped sequences (`&`, `<`, `>`, `"`, `'`) followed by a single character-class regex `[<>"']` with a lookup function for the rest — instead of five sequential single-character regex passes. A scalar fast-path also skips `iterateFilter.apply` when input is null, undefined, or a non-object. Output is byte-identical to the previous behavior on every input, including the entity-preservation semantics swig has shipped since the upstream fork. Measured against `benchmarks/render.js` (medians of 5 runs, autoescape on, Node 25), simple-var-output goes from ~2.46M to 3.86M ops/s (+57%, flipping the verdict from `nunjucks 1.32x faster` to `swig 1.18x faster`); filter chain +37%; for-loop (5 items) +54%; if/else branch +71%; nested for+if+filter +56%. The `case 'js'` branch and array-iteration fallback through `iterateFilter` are unchanged. All 1443 tests pass including the 9 CVE regressions.
|
package/HISTORY.md
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
[2.2.0](https://github.com/gina-io/swig/tree/v2.2.0) / 2026-05-11
|
|
2
|
+
-----------------------------------------------------------------
|
|
3
|
+
|
|
4
|
+
* **Added** `renderFile(path, locals, cb)` and `compileFile(path, options, cb)` automatically route to the async-codegen path when the configured loader signals async support via `loader.async === true`. The async path defers template resolution from parse time to render time via a new `_swig.getTemplate(path, options)` runtime helper that returns `Promise<TemplateFn>` — `extends`, `include`, `import`, and `from` emit deferred IR shapes from the frontend (`IRExtendsDeferred`, `IRIncludeDeferred`, `IRImportDeferred`, `IRFromImportDeferred`) and the shared backend wraps the compiled body in an `AsyncFunction`. Block overrides thread through the inheritance chain via a sixth `_blocks` positional argument on the wrapped template function; macro imports pick up exports via the new `Promise<{output, exports}>` template-fn return shape. Both `@rhinostone/swig` and `@rhinostone/swig-twig` flavors — parity across the two surfaces. Static template targets (string literals in `extends` / `include` / `import` / `from`) work end-to-end against async loaders; dynamic targets (`{% extends parent_var %}`, `{% include user_template %}`) are not yet supported on the async path and surface a clear runtime error with the unresolved expression visible in the message — full dynamic-target support is tracked as a follow-up. The sync render path is unchanged — loaders without `loader.async === true` continue to use the established sync `_swig.compileFile(...)` resolution, including the built-in `loaders.fs` and `loaders.memory` which remain dual-mode. End-to-end coverage at `tests/async/render-file-cb-dispatch.test.js` (native, 11 cases) and `tests/swig-twig/async/render-file-cb-dispatch.test.js` (Twig, 11 cases) covers static extends chains, includes, macro imports, mixed graphs, dynamic include paths surfacing the runtime error, the `ignoreMissing` flag, `with-context` isolation, bare-name and aliased `from` import, and error propagation.
|
|
5
|
+
|
|
6
|
+
* **Changed** `renderFileAsync(path, locals, cb)` and `compileFileAsync(path, options, cb)` on both `@rhinostone/swig` and `@rhinostone/swig-twig` are soft-deprecated via JSDoc only — no runtime warning, no `console.warn`. Use `renderFile(path, locals, cb)` / `compileFile(path, options, cb)` with an async loader (`loader.async === true`) instead; the dispatch is automatic. The legacy pre-walker entry points remain fully functional in 2.x and will be removed in 3.0.
|
|
7
|
+
|
|
8
|
+
* **Changed** Improved the performance of the `escape` / `e` filter in both `@rhinostone/swig` and `@rhinostone/swig-twig`. The HTML default branch now runs a two-pass form — an entity-aware first pass that preserves already-escaped sequences (`&`, `<`, `>`, `"`, `'`) followed by a single character-class regex `[<>"']` with a lookup function for the rest — instead of five sequential single-character regex passes. A scalar fast-path also skips `iterateFilter.apply` when input is null, undefined, or a non-object. Output is byte-identical to the previous behavior on every input, including the entity-preservation semantics swig has shipped since the upstream fork. Measured against `benchmarks/render.js` (medians of 5 runs, autoescape on, Node 25), simple-var-output goes from ~2.46M to 3.86M ops/s (+57%, flipping the verdict from `nunjucks 1.32x faster` to `swig 1.18x faster`); filter chain +37%; for-loop (5 items) +54%; if/else branch +71%; nested for+if+filter +56%. The `case 'js'` branch and array-iteration fallback through `iterateFilter` are unchanged. All 1443 tests pass including the 9 CVE regressions.
|
|
9
|
+
|
|
1
10
|
[2.1.0](https://github.com/gina-io/swig/tree/v2.1.0) / 2026-05-10
|
|
2
11
|
-----------------------------------------------------------------
|
|
3
12
|
|
package/ROADMAP.md
CHANGED
|
@@ -14,6 +14,7 @@ _No near-term scheduled items. See [Future (post-2.0)](#future-post-20) for upco
|
|
|
14
14
|
|
|
15
15
|
| Status | Item |
|
|
16
16
|
| --- | --- |
|
|
17
|
+
| Planned | Async parse path for dynamic targets — full support for `{% extends parent_var %}`, `{% include user_template %}`, and runtime-resolved `import` / `from` paths on the async-codegen branch. Static-target async dispatch shipped in 2.2.0; dynamic-target support is on hold pending consumer demand. |
|
|
17
18
|
| Planned | Ship Jinja2 and Django frontends as additional `@rhinostone/swig-*` packages. On demand — when there's concrete user demand. |
|
|
18
19
|
| Planned | Test framework migration. Replace mocha 1.x + expect.js with `node:test` + `node:assert/strict`, swap mocha-phantomjs for a modern browser-test harness, swap blanket for `c8`. (The Node engines bump is upstream-driven by gina and is being treated as done.) |
|
|
19
20
|
|
|
@@ -21,6 +22,12 @@ _No near-term scheduled items. See [Future (post-2.0)](#future-post-20) for upco
|
|
|
21
22
|
|
|
22
23
|
## Completed
|
|
23
24
|
|
|
25
|
+
### v2.2.0 (May 2026)
|
|
26
|
+
|
|
27
|
+
- `renderFile(path, locals, cb)` and `compileFile(path, options, cb)` now automatically route to the async-codegen path when the configured loader signals async support via `loader.async === true`. The async path defers template resolution from parse time to render time via a new `_swig.getTemplate(path, options)` runtime helper that returns `Promise<TemplateFn>`; `extends`, `include`, `import`, and `from` emit deferred IR shapes and the shared backend wraps the compiled body in an `AsyncFunction`. Block overrides thread through the inheritance chain via a sixth `_blocks` positional argument; macro imports pick up exports via a `Promise<{output, exports}>` template-fn return shape. Both `@rhinostone/swig` and `@rhinostone/swig-twig` flavors — parity across the two surfaces. Static template targets (string literals in `extends` / `include` / `import` / `from`) work end-to-end against async loaders; dynamic targets surface a clear runtime error and are tracked as a follow-up. The sync render path is unchanged — loaders without `loader.async === true` continue to use the established sync `_swig.compileFile(...)` resolution, including the built-in `loaders.fs` and `loaders.memory` which remain dual-mode.
|
|
28
|
+
- `renderFileAsync(path, locals, cb)` and `compileFileAsync(path, options, cb)` on both `@rhinostone/swig` and `@rhinostone/swig-twig` are soft-deprecated via JSDoc only — no runtime warning. Use `renderFile` / `compileFile` with an async loader (`loader.async === true`) instead; the dispatch is automatic. The legacy pre-walker entry points remain fully functional in 2.x and will be removed in 3.0.
|
|
29
|
+
- Performance improvement to the `escape` / `e` filter in both flavors. The HTML default branch switched from a five-replace chain to an entity-preserving two-pass form (entity-aware first pass that preserves already-escaped sequences, followed by a single character-class regex with a lookup function). A scalar fast-path skips the array/object iteration when input is null, undefined, or a non-object. Output is byte-identical to the previous behavior on every input. Measured against `benchmarks/render.js` (medians of 5 runs, autoescape on, Node 25): simple-var-output `+57%` (flipping the bench verdict from `nunjucks 1.32x faster` to `swig 1.18x faster`); filter chain `+37%`; for-loop (5 items) `+54%`; if/else branch `+71%`; nested for+if+filter `+56%`.
|
|
30
|
+
|
|
24
31
|
### v2.1.0 (May 2026)
|
|
25
32
|
|
|
26
33
|
- 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.
|
package/dist/swig.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! Swig v2.
|
|
1
|
+
/*! Swig v2.2.0 | https://github.com/gina-io/swig | @license https://github.com/gina-io/swig/blob/master/LICENSE */
|
|
2
2
|
/*! DateZ (c) 2011 Tomo Universalis | @license https://github.com/ocrybit/DateZ/blob/master/LISENCE */
|
|
3
3
|
(() => {
|
|
4
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
@@ -844,32 +844,40 @@
|
|
|
844
844
|
exports["default"] = function(input, def) {
|
|
845
845
|
return typeof input !== "undefined" && (input || typeof input === "number") ? input : def;
|
|
846
846
|
};
|
|
847
|
+
function escapeHtmlRest(ch) {
|
|
848
|
+
return ch === "<" ? "<" : ch === ">" ? ">" : ch === '"' ? """ : "'";
|
|
849
|
+
}
|
|
847
850
|
exports.escape = function(input, type) {
|
|
848
|
-
var
|
|
849
|
-
if (
|
|
850
|
-
return
|
|
851
|
+
var t, inp, out, i, code;
|
|
852
|
+
if (input === null || input === void 0) {
|
|
853
|
+
return input;
|
|
851
854
|
}
|
|
852
|
-
|
|
855
|
+
t = typeof input;
|
|
856
|
+
if (t !== "string") {
|
|
857
|
+
if (t === "object") {
|
|
858
|
+
out = iterateFilter.apply(exports.escape, arguments);
|
|
859
|
+
if (out !== void 0) {
|
|
860
|
+
return out;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
853
863
|
return input;
|
|
854
864
|
}
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
out += inp[i];
|
|
867
|
-
}
|
|
865
|
+
if (type === "js") {
|
|
866
|
+
inp = input.replace(/\\/g, "\\u005C");
|
|
867
|
+
out = "";
|
|
868
|
+
for (i = 0; i < inp.length; i += 1) {
|
|
869
|
+
code = inp.charCodeAt(i);
|
|
870
|
+
if (code < 32) {
|
|
871
|
+
code = code.toString(16).toUpperCase();
|
|
872
|
+
code = code.length < 2 ? "0" + code : code;
|
|
873
|
+
out += "\\u00" + code;
|
|
874
|
+
} else {
|
|
875
|
+
out += inp[i];
|
|
868
876
|
}
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
return inp.replace(/&(?!amp;|lt;|gt;|quot;|#39;)/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
877
|
+
}
|
|
878
|
+
return out.replace(/&/g, "\\u0026").replace(/</g, "\\u003C").replace(/>/g, "\\u003E").replace(/\'/g, "\\u0027").replace(/"/g, "\\u0022").replace(/\=/g, "\\u003D").replace(/-/g, "\\u002D").replace(/;/g, "\\u003B");
|
|
872
879
|
}
|
|
880
|
+
return input.replace(/&(?!amp;|lt;|gt;|quot;|#39;)/g, "&").replace(/[<>"']/g, escapeHtmlRest);
|
|
873
881
|
};
|
|
874
882
|
exports.e = exports.escape;
|
|
875
883
|
exports.first = function(input) {
|
|
@@ -2993,9 +3001,19 @@
|
|
|
2993
3001
|
var require_import = __commonJS({
|
|
2994
3002
|
"lib/tags/import.js"(exports) {
|
|
2995
3003
|
var utils = require_utils2();
|
|
3004
|
+
var ir = require_ir();
|
|
2996
3005
|
var backend = require_backend();
|
|
2997
3006
|
var _dangerousProps = require_security().dangerousProps;
|
|
2998
|
-
exports.compile = function(compiler, args) {
|
|
3007
|
+
exports.compile = function(compiler, args, content, parents, options) {
|
|
3008
|
+
if (options && options.codegenMode === "async") {
|
|
3009
|
+
var asyncAlias = args[args.length - 1];
|
|
3010
|
+
var asyncPath = args[0].path;
|
|
3011
|
+
return ir.importDeferred(
|
|
3012
|
+
ir.literal("string", asyncPath),
|
|
3013
|
+
asyncAlias,
|
|
3014
|
+
options.filename || ""
|
|
3015
|
+
);
|
|
3016
|
+
}
|
|
2999
3017
|
var ctx = args.pop(), allMacros = utils.map(args, function(arg) {
|
|
3000
3018
|
return arg.name;
|
|
3001
3019
|
}).join("|"), out = "_ctx." + ctx + ' = {};\n var _output = "";\n', replacements = utils.map(args, function(arg) {
|
|
@@ -3014,27 +3032,31 @@
|
|
|
3014
3032
|
return out;
|
|
3015
3033
|
};
|
|
3016
3034
|
exports.parse = function(str, line, parser, types, stack, opts, swig2) {
|
|
3017
|
-
var compiler = require_parser().compile, parseOpts = { resolveFrom: opts.filename }, compileOpts = utils.extend({}, opts, parseOpts),
|
|
3035
|
+
var compiler = require_parser().compile, parseOpts = { resolveFrom: opts.filename }, compileOpts = utils.extend({}, opts, parseOpts), isAsync = !!(opts && opts.codegenMode === "async"), importPath, ctx;
|
|
3018
3036
|
parser.on(types.STRING, function(token) {
|
|
3019
3037
|
var self = this;
|
|
3020
|
-
if (
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
}
|
|
3027
|
-
macroName = token2.args[0];
|
|
3028
|
-
out += backend.compile([token2.compile(compiler, token2.args, token2.content, [], compileOpts)], [], compileOpts) + "\n";
|
|
3029
|
-
self.out.push({ compiled: out, name: macroName });
|
|
3030
|
-
});
|
|
3038
|
+
if (importPath !== void 0) {
|
|
3039
|
+
throw new Error("Unexpected string " + token.match + " on line " + line + ".");
|
|
3040
|
+
}
|
|
3041
|
+
importPath = token.match.replace(/^("|')|("|')$/g, "");
|
|
3042
|
+
if (isAsync) {
|
|
3043
|
+
self.out.push({ path: importPath });
|
|
3031
3044
|
return;
|
|
3032
3045
|
}
|
|
3033
|
-
|
|
3046
|
+
var tokens = swig2.parseFile(importPath, parseOpts).tokens;
|
|
3047
|
+
utils.each(tokens, function(token2) {
|
|
3048
|
+
var out = "", macroName;
|
|
3049
|
+
if (!token2 || token2.name !== "macro" || !token2.compile) {
|
|
3050
|
+
return;
|
|
3051
|
+
}
|
|
3052
|
+
macroName = token2.args[0];
|
|
3053
|
+
out += backend.compile([token2.compile(compiler, token2.args, token2.content, [], compileOpts)], [], compileOpts) + "\n";
|
|
3054
|
+
self.out.push({ compiled: out, name: macroName });
|
|
3055
|
+
});
|
|
3034
3056
|
});
|
|
3035
3057
|
parser.on(types.VAR, function(token) {
|
|
3036
3058
|
var self = this;
|
|
3037
|
-
if (
|
|
3059
|
+
if (importPath === void 0 || ctx) {
|
|
3038
3060
|
throw new Error('Unexpected variable "' + token.match + '" on line ' + line + ".");
|
|
3039
3061
|
}
|
|
3040
3062
|
if (token.match === "as") {
|
|
@@ -3071,6 +3093,9 @@
|
|
|
3071
3093
|
w = void 0;
|
|
3072
3094
|
}
|
|
3073
3095
|
}
|
|
3096
|
+
if (options && options.codegenMode === "async") {
|
|
3097
|
+
return ir.includeDeferred(file, w || void 0, !!onlyCtx, !!ignoreMissing, parentFile);
|
|
3098
|
+
}
|
|
3074
3099
|
return ir.include(file, w || void 0, !!onlyCtx, !!ignoreMissing, parentFile);
|
|
3075
3100
|
};
|
|
3076
3101
|
exports.lowerExpr = function(parser, tokens) {
|
|
@@ -4130,6 +4155,7 @@
|
|
|
4130
4155
|
var require_engine = __commonJS({
|
|
4131
4156
|
"packages/swig-core/lib/engine.js"(exports) {
|
|
4132
4157
|
var utils = require_utils();
|
|
4158
|
+
var ir = require_ir();
|
|
4133
4159
|
var backend = require_backend();
|
|
4134
4160
|
var cache = require_cache();
|
|
4135
4161
|
var AsyncFunction = Object.getPrototypeOf(async function() {
|
|
@@ -4160,6 +4186,45 @@
|
|
|
4160
4186
|
}
|
|
4161
4187
|
});
|
|
4162
4188
|
};
|
|
4189
|
+
function buildExtendsDeferred(tokens, options) {
|
|
4190
|
+
var childBlocks = {};
|
|
4191
|
+
var childIRs = [];
|
|
4192
|
+
utils.each(tokens.blocks, function(blockToken) {
|
|
4193
|
+
var args = blockToken.args ? blockToken.args.slice(0) : [];
|
|
4194
|
+
var content = blockToken.content ? blockToken.content.slice(0) : [];
|
|
4195
|
+
if (blockToken.name === "block") {
|
|
4196
|
+
var blockName = args.join("");
|
|
4197
|
+
var blockIR = blockToken.compile(backend.compile, args, content, [], options, blockName, blockToken);
|
|
4198
|
+
if (blockIR && typeof blockIR === "object" && typeof blockIR.type === "string") {
|
|
4199
|
+
childBlocks[blockName] = blockIR;
|
|
4200
|
+
}
|
|
4201
|
+
return;
|
|
4202
|
+
}
|
|
4203
|
+
var result = blockToken.compile(backend.compile, args, content, [], options, void 0, blockToken);
|
|
4204
|
+
if (result === void 0 || result === null || result === "") {
|
|
4205
|
+
return;
|
|
4206
|
+
}
|
|
4207
|
+
if (typeof result === "string") {
|
|
4208
|
+
childIRs.push(ir.legacyJS(result));
|
|
4209
|
+
return;
|
|
4210
|
+
}
|
|
4211
|
+
if (utils.isArray(result)) {
|
|
4212
|
+
utils.each(result, function(n) {
|
|
4213
|
+
childIRs.push(n);
|
|
4214
|
+
});
|
|
4215
|
+
return;
|
|
4216
|
+
}
|
|
4217
|
+
if (typeof result === "object" && typeof result.type === "string") {
|
|
4218
|
+
childIRs.push(result);
|
|
4219
|
+
}
|
|
4220
|
+
});
|
|
4221
|
+
return ir.extendsDeferred(
|
|
4222
|
+
ir.literal("string", tokens.parent),
|
|
4223
|
+
childBlocks,
|
|
4224
|
+
childIRs,
|
|
4225
|
+
options.filename || ""
|
|
4226
|
+
);
|
|
4227
|
+
}
|
|
4163
4228
|
exports.getParents = function(tokens, options, deps) {
|
|
4164
4229
|
var parentName = tokens.parent, parentFiles = [], parents = [], parentFile, parent, l;
|
|
4165
4230
|
while (parentName) {
|
|
@@ -4267,10 +4332,16 @@
|
|
|
4267
4332
|
return self.parse(src, options);
|
|
4268
4333
|
};
|
|
4269
4334
|
self.precompile = function(source, options) {
|
|
4270
|
-
var tokens = self.parse(source, options), parents
|
|
4271
|
-
if (
|
|
4272
|
-
|
|
4273
|
-
|
|
4335
|
+
var tokens = self.parse(source, options), parents, tpl;
|
|
4336
|
+
if (options && options.codegenMode === "async" && tokens.parent) {
|
|
4337
|
+
parents = [];
|
|
4338
|
+
tokens.tokens = [buildExtendsDeferred(tokens, options)];
|
|
4339
|
+
} else {
|
|
4340
|
+
parents = getParentsInternal(tokens, options);
|
|
4341
|
+
if (parents.length) {
|
|
4342
|
+
tokens.tokens = exports.remapBlocks(tokens.blocks, parents[0].tokens);
|
|
4343
|
+
exports.importNonBlocks(tokens.blocks, tokens.tokens);
|
|
4344
|
+
}
|
|
4274
4345
|
}
|
|
4275
4346
|
try {
|
|
4276
4347
|
tpl = exports.buildTemplateFunction(tokens, parents, options);
|
|
@@ -4394,6 +4465,14 @@
|
|
|
4394
4465
|
};
|
|
4395
4466
|
self.renderFile = function(pathName, locals, cb) {
|
|
4396
4467
|
if (cb) {
|
|
4468
|
+
if (self.options.loader && self.options.loader.async === true) {
|
|
4469
|
+
self.getTemplate(pathName).then(function(fn) {
|
|
4470
|
+
return fn(locals);
|
|
4471
|
+
}).then(function(result) {
|
|
4472
|
+
cb(null, result.output);
|
|
4473
|
+
}).catch(cb);
|
|
4474
|
+
return;
|
|
4475
|
+
}
|
|
4397
4476
|
self.compileFile(pathName, {}, function(err, fn) {
|
|
4398
4477
|
var result;
|
|
4399
4478
|
if (err) {
|
|
@@ -4434,7 +4513,7 @@
|
|
|
4434
4513
|
var loaders = require_loaders2();
|
|
4435
4514
|
var preWalker = require_pre_walker();
|
|
4436
4515
|
var engine = require_engine();
|
|
4437
|
-
exports.version = "2.
|
|
4516
|
+
exports.version = "2.2.0";
|
|
4438
4517
|
var defaultOptions = {
|
|
4439
4518
|
autoescape: true,
|
|
4440
4519
|
varControls: ["{{", "}}"],
|