@rhinostone/swig-core 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.
Files changed (2) hide show
  1. package/lib/engine.js +105 -4
  2. package/package.json +1 -1
package/lib/engine.js CHANGED
@@ -1,4 +1,5 @@
1
1
  var utils = require('./utils'),
2
+ ir = require('./ir'),
2
3
  backend = require('./backend'),
3
4
  cache = require('./cache');
4
5
 
@@ -79,6 +80,82 @@ exports.importNonBlocks = function (blocks, tokens) {
79
80
  });
80
81
  };
81
82
 
83
+ /**
84
+ * Build an `IRExtendsDeferred` node from a parsed child template.
85
+ *
86
+ * Walks `tokens.blocks` (the parser's map of every block-level tag that
87
+ * appeared at top level — both `{% block %}` overrides and non-block
88
+ * preludes like `{% set %}`, `{% import %}`, `{% macro %}`) and:
89
+ *
90
+ * - For each `{% block %}` token: invokes its `.compile(...)` with an
91
+ * empty parents list to obtain an `IRBlock` (body is one `IRLegacyJS`
92
+ * wrapping the recursively-compiled JS source — same shape sync mode
93
+ * produces). The IRBlock is keyed under the block name in `childBlocks`.
94
+ * - For each non-`block` block-level token: invokes its `.compile(...)`
95
+ * and wraps any JS-string return in `IRLegacyJS`. The resulting IR
96
+ * node is pushed onto `childIRs` (the prelude list).
97
+ *
98
+ * The backend's `IRExtendsDeferred` emit branch handles runtime parent
99
+ * resolution via `_swig.getTemplate` and the `_blocks` parameter contract
100
+ * (see `packages/swig-core/lib/backend.js`).
101
+ *
102
+ * `tokens.parent` is lifted into an `IRLiteral('string', …)` so the
103
+ * backend's `emitExpr` path produces a quoted JS string literal. The
104
+ * native parser stashes `tokens.parent` as the literal text of the
105
+ * extends argument after stripping enclosing quotes (lib/parser.js:273),
106
+ * which works for `{% extends "layout.html" %}` but for a bare
107
+ * identifier (`{% extends parent_var %}`) yields a pre-lowered JS
108
+ * source fragment such as `((typeof _ctx.parent_var !== "undefined")
109
+ * ? _ctx.parent_var : …)`. Embedded here as a string literal it
110
+ * becomes a garbage template lookup at runtime
111
+ * (`Template not found: /((typeof _ctx.parent_var …`). Closing the
112
+ * dynamic-extends gap requires the parser to stash an IRExpr on
113
+ * `tokens.parent` and this helper to lower it through
114
+ * `ir.extendsDeferred`'s `parentExpr` slot — already designed as
115
+ * `<IRExpr>` per the deferred IR contract.
116
+ *
117
+ * @param {object} tokens Parsed child template (must have `.parent`).
118
+ * @param {object} options Per-call Swig options; `options.filename` is
119
+ * used as the deferred resolveFrom.
120
+ * @return {object} IRExtendsDeferred node.
121
+ * @private
122
+ */
123
+ function buildExtendsDeferred(tokens, options) {
124
+ var childBlocks = {};
125
+ var childIRs = [];
126
+ utils.each(tokens.blocks, function (blockToken) {
127
+ var args = blockToken.args ? blockToken.args.slice(0) : [];
128
+ var content = blockToken.content ? blockToken.content.slice(0) : [];
129
+ if (blockToken.name === 'block') {
130
+ var blockName = args.join('');
131
+ var blockIR = blockToken.compile(backend.compile, args, content, [], options, blockName, blockToken);
132
+ if (blockIR && typeof blockIR === 'object' && typeof blockIR.type === 'string') {
133
+ childBlocks[blockName] = blockIR;
134
+ }
135
+ return;
136
+ }
137
+ var result = blockToken.compile(backend.compile, args, content, [], options, undefined, blockToken);
138
+ if (result === undefined || result === null || result === '') { return; }
139
+ if (typeof result === 'string') {
140
+ childIRs.push(ir.legacyJS(result));
141
+ return;
142
+ }
143
+ if (utils.isArray(result)) {
144
+ utils.each(result, function (n) { childIRs.push(n); });
145
+ return;
146
+ }
147
+ if (typeof result === 'object' && typeof result.type === 'string') {
148
+ childIRs.push(result);
149
+ }
150
+ });
151
+ return ir.extendsDeferred(
152
+ ir.literal('string', tokens.parent),
153
+ childBlocks,
154
+ childIRs,
155
+ options.filename || ''
156
+ );
157
+ }
158
+
82
159
  /**
83
160
  * Walk a template's `extends` chain and build the parent token tree.
84
161
  * Detects circular inheritance.
@@ -307,12 +384,24 @@ exports.install = function (self, frontend) {
307
384
 
308
385
  self.precompile = function (source, options) {
309
386
  var tokens = self.parse(source, options),
310
- parents = getParentsInternal(tokens, options),
387
+ parents,
311
388
  tpl;
312
389
 
313
- if (parents.length) {
314
- tokens.tokens = exports.remapBlocks(tokens.blocks, parents[0].tokens);
315
- exports.importNonBlocks(tokens.blocks, tokens.tokens);
390
+ if (options && options.codegenMode === 'async' && tokens.parent) {
391
+ // Async extends — defer parent walking and block remapping to
392
+ // runtime via the IRExtendsDeferred emit branch in backend.js.
393
+ // The deferred node carries the child's blocks + non-block
394
+ // preludes; the backend resolves the parent via _swig.getTemplate
395
+ // and threads block overrides through the _blocks parameter
396
+ // contract.
397
+ parents = [];
398
+ tokens.tokens = [buildExtendsDeferred(tokens, options)];
399
+ } else {
400
+ parents = getParentsInternal(tokens, options);
401
+ if (parents.length) {
402
+ tokens.tokens = exports.remapBlocks(tokens.blocks, parents[0].tokens);
403
+ exports.importNonBlocks(tokens.blocks, tokens.tokens);
404
+ }
316
405
  }
317
406
 
318
407
  try {
@@ -497,6 +586,18 @@ exports.install = function (self, frontend) {
497
586
 
498
587
  self.renderFile = function (pathName, locals, cb) {
499
588
  if (cb) {
589
+ // Async loader opt-in: route through getTemplate when
590
+ // loader.async === true. Explicit flag only — load.length is
591
+ // not a dispatch signal (the built-in fs + memory loaders are
592
+ // dual-mode with length 2 and must keep the sync-cb path).
593
+ if (self.options.loader && self.options.loader.async === true) {
594
+ self.getTemplate(pathName)
595
+ .then(function (fn) { return fn(locals); })
596
+ .then(function (result) { cb(null, result.output); })
597
+ .catch(cb);
598
+ return;
599
+ }
600
+
500
601
  self.compileFile(pathName, {}, function (err, fn) {
501
602
  var result;
502
603
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rhinostone/swig-core",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "Shared IR, backend, and runtime for the @rhinostone/swig family of template engines.",
5
5
  "keywords": [
6
6
  "template",