sommark 4.4.0 → 4.5.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/core/helpers/lib.js +1 -1
- package/core/modules.js +1 -0
- package/core/transpiler.js +94 -9
- package/dist/sommark.browser.js +99 -11
- package/dist/sommark.browser.lite.js +98 -10
- package/index.shared.js +3 -1
- package/package.json +1 -1
package/core/helpers/lib.js
CHANGED
package/core/modules.js
CHANGED
|
@@ -366,6 +366,7 @@ export async function resolveModules(ast, context) {
|
|
|
366
366
|
security: context.instance.security,
|
|
367
367
|
showSpinner: context.instance.showSpinner,
|
|
368
368
|
importStack: [...stack, absFilename],
|
|
369
|
+
moduleIdentityToken: context.instance.moduleIdentityToken,
|
|
369
370
|
moduleCache: context.instance.moduleCache
|
|
370
371
|
});
|
|
371
372
|
|
package/core/transpiler.js
CHANGED
|
@@ -45,7 +45,7 @@ function getNodeText(node) {
|
|
|
45
45
|
* @param {Object} mapper_file - The rules for how to convert each node.
|
|
46
46
|
* @returns {Promise<string>} - The final text for this node.
|
|
47
47
|
*/
|
|
48
|
-
async function generateOutput(ast, i, format, mapper_file, security = {}, parentId = null, generateRuntimeOutput = false, hideRuntimeOutput = false, instance = null) {
|
|
48
|
+
async function generateOutput(ast, i, format, mapper_file, security = {}, parentId = null, generateRuntimeOutput = false, hideRuntimeOutput = false, instance = null, idState = null) {
|
|
49
49
|
const node = Array.isArray(ast) ? ast[i] : ast;
|
|
50
50
|
if (!node) return "";
|
|
51
51
|
|
|
@@ -60,7 +60,7 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
|
|
|
60
60
|
if (node.body) {
|
|
61
61
|
evaluator.pushScope();
|
|
62
62
|
for (let j = 0; j < node.body.length; j++) {
|
|
63
|
-
bodyOutput += await generateOutput(node.body, j, format, mapper_file, security, parentId, generateRuntimeOutput, hideRuntimeOutput, instance);
|
|
63
|
+
bodyOutput += await generateOutput(node.body, j, format, mapper_file, security, parentId, generateRuntimeOutput, hideRuntimeOutput, instance, idState);
|
|
64
64
|
}
|
|
65
65
|
await evaluator.popScope();
|
|
66
66
|
}
|
|
@@ -168,7 +168,7 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
|
|
|
168
168
|
});
|
|
169
169
|
|
|
170
170
|
for (let j = 0; j < cleanedBody.length; j++) {
|
|
171
|
-
output += await generateOutput(cleanedBody, j, format, mapper_file, security, parentId, generateRuntimeOutput, hideRuntimeOutput, instance);
|
|
171
|
+
output += await generateOutput(cleanedBody, j, format, mapper_file, security, parentId, generateRuntimeOutput, hideRuntimeOutput, instance, idState);
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
await evaluator.popScope();
|
|
@@ -191,7 +191,12 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
|
|
|
191
191
|
|
|
192
192
|
const hasRuntime = node.body?.some(child => child.type === RUNTIME_LOGIC);
|
|
193
193
|
if (hasRuntime) {
|
|
194
|
-
|
|
194
|
+
if (idState?.mode === 'replay') {
|
|
195
|
+
secretId = idState.ids[idState.idx++] ?? `sommark-${node.id.toLowerCase()}-${randomBytesHex(4)}`;
|
|
196
|
+
} else {
|
|
197
|
+
secretId = `sommark-${node.id.toLowerCase()}-${randomBytesHex(4)}`;
|
|
198
|
+
if (idState?.mode === 'record') idState.ids.push(secretId);
|
|
199
|
+
}
|
|
195
200
|
}
|
|
196
201
|
}
|
|
197
202
|
|
|
@@ -247,7 +252,7 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
|
|
|
247
252
|
let resolvedBody = "";
|
|
248
253
|
evaluator.pushScope();
|
|
249
254
|
for (let j = 0; j < node.body.length; j++) {
|
|
250
|
-
resolvedBody += await generateOutput(node.body, j, format, mapper_file, security, parentId, generateRuntimeOutput, hideRuntimeOutput, instance);
|
|
255
|
+
resolvedBody += await generateOutput(node.body, j, format, mapper_file, security, parentId, generateRuntimeOutput, hideRuntimeOutput, instance, idState);
|
|
251
256
|
}
|
|
252
257
|
await evaluator.popScope();
|
|
253
258
|
content = dedentBy(resolvedBody, node.range?.start?.character || 0);
|
|
@@ -257,7 +262,7 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
|
|
|
257
262
|
let childrenOutput = "";
|
|
258
263
|
if (node.body) {
|
|
259
264
|
for (let j = 0; j < node.body.length; j++) {
|
|
260
|
-
childrenOutput += await generateOutput(node.body, j, format, mapper_file, security, secretId || parentId, generateRuntimeOutput, hideRuntimeOutput, instance);
|
|
265
|
+
childrenOutput += await generateOutput(node.body, j, format, mapper_file, security, secretId || parentId, generateRuntimeOutput, hideRuntimeOutput, instance, idState);
|
|
261
266
|
}
|
|
262
267
|
}
|
|
263
268
|
return childrenOutput;
|
|
@@ -369,7 +374,7 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
|
|
|
369
374
|
|
|
370
375
|
case FOR_EACH:
|
|
371
376
|
case BLOCK:
|
|
372
|
-
bodyOutput = await generateOutput(body_node, 0, format, mapper_file, security, secretId || parentId, generateRuntimeOutput, hideRuntimeOutput, instance);
|
|
377
|
+
bodyOutput = await generateOutput(body_node, 0, format, mapper_file, security, secretId || parentId, generateRuntimeOutput, hideRuntimeOutput, instance, idState);
|
|
373
378
|
break;
|
|
374
379
|
|
|
375
380
|
case RUNTIME_LOGIC:
|
|
@@ -488,6 +493,31 @@ export async function transpiler(optionsOrAst, format, mapperFile) {
|
|
|
488
493
|
settings.fs = instance.fs;
|
|
489
494
|
}
|
|
490
495
|
|
|
496
|
+
const generateRuntimeOutput = optionsOrAst?.generateRuntimeOutput || false;
|
|
497
|
+
const hideRuntimeOutput = optionsOrAst?.hideRuntimeOutput || false;
|
|
498
|
+
const dualOutput = optionsOrAst?.dualOutput || false;
|
|
499
|
+
|
|
500
|
+
if (dualOutput && (generateRuntimeOutput || hideRuntimeOutput)) {
|
|
501
|
+
const flags = [
|
|
502
|
+
generateRuntimeOutput && "\x1b[36mgenerateRuntimeOutput\x1b[0m",
|
|
503
|
+
hideRuntimeOutput && "\x1b[36mhideRuntimeOutput\x1b[0m"
|
|
504
|
+
].filter(Boolean).join(" and ");
|
|
505
|
+
console.warn(
|
|
506
|
+
`\n[SomMark] \x1b[33m⚠ Ignored options when dualOutput is true\x1b[0m\n` +
|
|
507
|
+
` ${flags} ${generateRuntimeOutput && hideRuntimeOutput ? "are" : "is"} ignored when \x1b[32mdualOutput: true\x1b[0m is set.\n` +
|
|
508
|
+
` \x1b[2mdualOutput manages both HTML and JS passes internally — no need to set those flags.\x1b[0m\n`
|
|
509
|
+
);
|
|
510
|
+
} else if (generateRuntimeOutput && hideRuntimeOutput) {
|
|
511
|
+
console.warn(
|
|
512
|
+
"\n[SomMark] \x1b[33m⚠ Conflicting options — output will be empty\x1b[0m\n" +
|
|
513
|
+
" \x1b[36mgenerateRuntimeOutput: true\x1b[0m → outputs only JS, suppresses all HTML\n" +
|
|
514
|
+
" \x1b[36mhideRuntimeOutput: true\x1b[0m → suppresses all JS output\n" +
|
|
515
|
+
" Together they cancel each other out and produce nothing.\n" +
|
|
516
|
+
" \x1b[2mHint: use one at a time, or \x1b[0m\x1b[32mdualOutput: true\x1b[0m\x1b[2m to get [html, js] in one call.\x1b[0m\n"
|
|
517
|
+
);
|
|
518
|
+
return "";
|
|
519
|
+
}
|
|
520
|
+
|
|
491
521
|
// Initialize Logic Sandbox
|
|
492
522
|
await evaluator.init(null, security, settings, targetMapper);
|
|
493
523
|
// Inject global data
|
|
@@ -499,8 +529,63 @@ export async function transpiler(optionsOrAst, format, mapperFile) {
|
|
|
499
529
|
let output = "";
|
|
500
530
|
let prev_body_node = null;
|
|
501
531
|
let prev_was_silent = false;
|
|
502
|
-
|
|
503
|
-
|
|
532
|
+
|
|
533
|
+
if (dualOutput) {
|
|
534
|
+
const idState = { mode: 'record', ids: [], idx: 0 };
|
|
535
|
+
|
|
536
|
+
// HTML pass — generate HTML, record element IDs for runtime blocks
|
|
537
|
+
let htmlOutput = "";
|
|
538
|
+
try {
|
|
539
|
+
for (let i = 0; i < body.length; i++) {
|
|
540
|
+
const node = body[i];
|
|
541
|
+
const blockOutput = await generateOutput(body, i, targetFormat, targetMapper, security, null, false, true, instance, idState);
|
|
542
|
+
let finalBlockOutput = blockOutput;
|
|
543
|
+
if (prev_was_silent && node.type === TEXT) finalBlockOutput = finalBlockOutput.replace(/^\n/, "");
|
|
544
|
+
if (finalBlockOutput) {
|
|
545
|
+
htmlOutput += finalBlockOutput;
|
|
546
|
+
prev_was_silent = false;
|
|
547
|
+
} else {
|
|
548
|
+
prev_was_silent = true;
|
|
549
|
+
if ((node.type === COMMENT || node.type === COMMENT_BLOCK) && targetMapper?.options?.removeComments) {
|
|
550
|
+
const nextNode = body[i + 1];
|
|
551
|
+
if (nextNode && nextNode.type === TEXT && (nextNode.text === "\n" || nextNode.text === "\r\n")) i++;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
} finally {
|
|
556
|
+
evaluator.destroy();
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// JS pass — replay the same IDs so querySelector targets match HTML
|
|
560
|
+
idState.mode = 'replay';
|
|
561
|
+
idState.idx = 0;
|
|
562
|
+
prev_was_silent = false;
|
|
563
|
+
|
|
564
|
+
await evaluator.init(null, security, settings, targetMapper);
|
|
565
|
+
evaluator.inject(placeholders);
|
|
566
|
+
evaluator.inject(variables);
|
|
567
|
+
|
|
568
|
+
let jsOutput = "";
|
|
569
|
+
try {
|
|
570
|
+
for (let i = 0; i < body.length; i++) {
|
|
571
|
+
const node = body[i];
|
|
572
|
+
const blockOutput = await generateOutput(body, i, targetFormat, targetMapper, security, null, true, false, instance, idState);
|
|
573
|
+
let finalBlockOutput = blockOutput;
|
|
574
|
+
if (prev_was_silent && node.type === TEXT) finalBlockOutput = finalBlockOutput.replace(/^\n/, "");
|
|
575
|
+
if (finalBlockOutput) {
|
|
576
|
+
jsOutput += finalBlockOutput;
|
|
577
|
+
prev_was_silent = false;
|
|
578
|
+
} else {
|
|
579
|
+
prev_was_silent = true;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
} finally {
|
|
583
|
+
evaluator.destroy();
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
return [htmlOutput.trim(), jsOutput.trim()];
|
|
587
|
+
}
|
|
588
|
+
|
|
504
589
|
try {
|
|
505
590
|
for (let i = 0; i < body.length; i++) {
|
|
506
591
|
const node = body[i];
|
package/dist/sommark.browser.js
CHANGED
|
@@ -9184,7 +9184,7 @@ function registerHostSettings(settings) {
|
|
|
9184
9184
|
hostSettings = settings || {};
|
|
9185
9185
|
}
|
|
9186
9186
|
|
|
9187
|
-
const version = "4.
|
|
9187
|
+
const version = "4.5.1";
|
|
9188
9188
|
|
|
9189
9189
|
const SomMark$1 = {
|
|
9190
9190
|
version,
|
|
@@ -10577,7 +10577,7 @@ function getNodeText$1(node) {
|
|
|
10577
10577
|
* @param {Object} mapper_file - The rules for how to convert each node.
|
|
10578
10578
|
* @returns {Promise<string>} - The final text for this node.
|
|
10579
10579
|
*/
|
|
10580
|
-
async function generateOutput(ast, i, format, mapper_file, security = {}, parentId = null, generateRuntimeOutput = false, hideRuntimeOutput = false, instance = null) {
|
|
10580
|
+
async function generateOutput(ast, i, format, mapper_file, security = {}, parentId = null, generateRuntimeOutput = false, hideRuntimeOutput = false, instance = null, idState = null) {
|
|
10581
10581
|
const node = Array.isArray(ast) ? ast[i] : ast;
|
|
10582
10582
|
if (!node) return "";
|
|
10583
10583
|
|
|
@@ -10592,7 +10592,7 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
|
|
|
10592
10592
|
if (node.body) {
|
|
10593
10593
|
Evaluator$1.pushScope();
|
|
10594
10594
|
for (let j = 0; j < node.body.length; j++) {
|
|
10595
|
-
bodyOutput += await generateOutput(node.body, j, format, mapper_file, security, parentId, generateRuntimeOutput, hideRuntimeOutput, instance);
|
|
10595
|
+
bodyOutput += await generateOutput(node.body, j, format, mapper_file, security, parentId, generateRuntimeOutput, hideRuntimeOutput, instance, idState);
|
|
10596
10596
|
}
|
|
10597
10597
|
await Evaluator$1.popScope();
|
|
10598
10598
|
}
|
|
@@ -10700,7 +10700,7 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
|
|
|
10700
10700
|
});
|
|
10701
10701
|
|
|
10702
10702
|
for (let j = 0; j < cleanedBody.length; j++) {
|
|
10703
|
-
output += await generateOutput(cleanedBody, j, format, mapper_file, security, parentId, generateRuntimeOutput, hideRuntimeOutput, instance);
|
|
10703
|
+
output += await generateOutput(cleanedBody, j, format, mapper_file, security, parentId, generateRuntimeOutput, hideRuntimeOutput, instance, idState);
|
|
10704
10704
|
}
|
|
10705
10705
|
|
|
10706
10706
|
await Evaluator$1.popScope();
|
|
@@ -10723,7 +10723,12 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
|
|
|
10723
10723
|
|
|
10724
10724
|
const hasRuntime = node.body?.some(child => child.type === RUNTIME_LOGIC);
|
|
10725
10725
|
if (hasRuntime) {
|
|
10726
|
-
|
|
10726
|
+
if (idState?.mode === 'replay') {
|
|
10727
|
+
secretId = idState.ids[idState.idx++] ?? `sommark-${node.id.toLowerCase()}-${randomBytesHex(4)}`;
|
|
10728
|
+
} else {
|
|
10729
|
+
secretId = `sommark-${node.id.toLowerCase()}-${randomBytesHex(4)}`;
|
|
10730
|
+
if (idState?.mode === 'record') idState.ids.push(secretId);
|
|
10731
|
+
}
|
|
10727
10732
|
}
|
|
10728
10733
|
}
|
|
10729
10734
|
|
|
@@ -10779,7 +10784,7 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
|
|
|
10779
10784
|
let resolvedBody = "";
|
|
10780
10785
|
Evaluator$1.pushScope();
|
|
10781
10786
|
for (let j = 0; j < node.body.length; j++) {
|
|
10782
|
-
resolvedBody += await generateOutput(node.body, j, format, mapper_file, security, parentId, generateRuntimeOutput, hideRuntimeOutput, instance);
|
|
10787
|
+
resolvedBody += await generateOutput(node.body, j, format, mapper_file, security, parentId, generateRuntimeOutput, hideRuntimeOutput, instance, idState);
|
|
10783
10788
|
}
|
|
10784
10789
|
await Evaluator$1.popScope();
|
|
10785
10790
|
content = dedentBy(resolvedBody, node.range?.start?.character || 0);
|
|
@@ -10789,7 +10794,7 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
|
|
|
10789
10794
|
let childrenOutput = "";
|
|
10790
10795
|
if (node.body) {
|
|
10791
10796
|
for (let j = 0; j < node.body.length; j++) {
|
|
10792
|
-
childrenOutput += await generateOutput(node.body, j, format, mapper_file, security, secretId || parentId, generateRuntimeOutput, hideRuntimeOutput, instance);
|
|
10797
|
+
childrenOutput += await generateOutput(node.body, j, format, mapper_file, security, secretId || parentId, generateRuntimeOutput, hideRuntimeOutput, instance, idState);
|
|
10793
10798
|
}
|
|
10794
10799
|
}
|
|
10795
10800
|
return childrenOutput;
|
|
@@ -10900,7 +10905,7 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
|
|
|
10900
10905
|
|
|
10901
10906
|
case FOR_EACH:
|
|
10902
10907
|
case BLOCK:
|
|
10903
|
-
bodyOutput = await generateOutput(body_node, 0, format, mapper_file, security, secretId || parentId, generateRuntimeOutput, hideRuntimeOutput, instance);
|
|
10908
|
+
bodyOutput = await generateOutput(body_node, 0, format, mapper_file, security, secretId || parentId, generateRuntimeOutput, hideRuntimeOutput, instance, idState);
|
|
10904
10909
|
break;
|
|
10905
10910
|
|
|
10906
10911
|
case RUNTIME_LOGIC:
|
|
@@ -11019,6 +11024,31 @@ async function transpiler(optionsOrAst, format, mapperFile) {
|
|
|
11019
11024
|
settings.fs = instance.fs;
|
|
11020
11025
|
}
|
|
11021
11026
|
|
|
11027
|
+
const generateRuntimeOutput = optionsOrAst?.generateRuntimeOutput || false;
|
|
11028
|
+
const hideRuntimeOutput = optionsOrAst?.hideRuntimeOutput || false;
|
|
11029
|
+
const dualOutput = optionsOrAst?.dualOutput || false;
|
|
11030
|
+
|
|
11031
|
+
if (dualOutput && (generateRuntimeOutput || hideRuntimeOutput)) {
|
|
11032
|
+
const flags = [
|
|
11033
|
+
generateRuntimeOutput && "\x1b[36mgenerateRuntimeOutput\x1b[0m",
|
|
11034
|
+
hideRuntimeOutput && "\x1b[36mhideRuntimeOutput\x1b[0m"
|
|
11035
|
+
].filter(Boolean).join(" and ");
|
|
11036
|
+
console.warn(
|
|
11037
|
+
`\n[SomMark] \x1b[33m⚠ Ignored options when dualOutput is true\x1b[0m\n` +
|
|
11038
|
+
` ${flags} ${generateRuntimeOutput && hideRuntimeOutput ? "are" : "is"} ignored when \x1b[32mdualOutput: true\x1b[0m is set.\n` +
|
|
11039
|
+
` \x1b[2mdualOutput manages both HTML and JS passes internally — no need to set those flags.\x1b[0m\n`
|
|
11040
|
+
);
|
|
11041
|
+
} else if (generateRuntimeOutput && hideRuntimeOutput) {
|
|
11042
|
+
console.warn(
|
|
11043
|
+
"\n[SomMark] \x1b[33m⚠ Conflicting options — output will be empty\x1b[0m\n" +
|
|
11044
|
+
" \x1b[36mgenerateRuntimeOutput: true\x1b[0m → outputs only JS, suppresses all HTML\n" +
|
|
11045
|
+
" \x1b[36mhideRuntimeOutput: true\x1b[0m → suppresses all JS output\n" +
|
|
11046
|
+
" Together they cancel each other out and produce nothing.\n" +
|
|
11047
|
+
" \x1b[2mHint: use one at a time, or \x1b[0m\x1b[32mdualOutput: true\x1b[0m\x1b[2m to get [html, js] in one call.\x1b[0m\n"
|
|
11048
|
+
);
|
|
11049
|
+
return "";
|
|
11050
|
+
}
|
|
11051
|
+
|
|
11022
11052
|
// Initialize Logic Sandbox
|
|
11023
11053
|
await Evaluator$1.init(null, security, settings, targetMapper);
|
|
11024
11054
|
// Inject global data
|
|
@@ -11030,8 +11060,63 @@ async function transpiler(optionsOrAst, format, mapperFile) {
|
|
|
11030
11060
|
let output = "";
|
|
11031
11061
|
let prev_body_node = null;
|
|
11032
11062
|
let prev_was_silent = false;
|
|
11033
|
-
|
|
11034
|
-
|
|
11063
|
+
|
|
11064
|
+
if (dualOutput) {
|
|
11065
|
+
const idState = { mode: 'record', ids: [], idx: 0 };
|
|
11066
|
+
|
|
11067
|
+
// HTML pass — generate HTML, record element IDs for runtime blocks
|
|
11068
|
+
let htmlOutput = "";
|
|
11069
|
+
try {
|
|
11070
|
+
for (let i = 0; i < body.length; i++) {
|
|
11071
|
+
const node = body[i];
|
|
11072
|
+
const blockOutput = await generateOutput(body, i, targetFormat, targetMapper, security, null, false, true, instance, idState);
|
|
11073
|
+
let finalBlockOutput = blockOutput;
|
|
11074
|
+
if (prev_was_silent && node.type === TEXT$1) finalBlockOutput = finalBlockOutput.replace(/^\n/, "");
|
|
11075
|
+
if (finalBlockOutput) {
|
|
11076
|
+
htmlOutput += finalBlockOutput;
|
|
11077
|
+
prev_was_silent = false;
|
|
11078
|
+
} else {
|
|
11079
|
+
prev_was_silent = true;
|
|
11080
|
+
if ((node.type === COMMENT || node.type === COMMENT_BLOCK) && targetMapper?.options?.removeComments) {
|
|
11081
|
+
const nextNode = body[i + 1];
|
|
11082
|
+
if (nextNode && nextNode.type === TEXT$1 && (nextNode.text === "\n" || nextNode.text === "\r\n")) i++;
|
|
11083
|
+
}
|
|
11084
|
+
}
|
|
11085
|
+
}
|
|
11086
|
+
} finally {
|
|
11087
|
+
Evaluator$1.destroy();
|
|
11088
|
+
}
|
|
11089
|
+
|
|
11090
|
+
// JS pass — replay the same IDs so querySelector targets match HTML
|
|
11091
|
+
idState.mode = 'replay';
|
|
11092
|
+
idState.idx = 0;
|
|
11093
|
+
prev_was_silent = false;
|
|
11094
|
+
|
|
11095
|
+
await Evaluator$1.init(null, security, settings, targetMapper);
|
|
11096
|
+
Evaluator$1.inject(placeholders);
|
|
11097
|
+
Evaluator$1.inject(variables);
|
|
11098
|
+
|
|
11099
|
+
let jsOutput = "";
|
|
11100
|
+
try {
|
|
11101
|
+
for (let i = 0; i < body.length; i++) {
|
|
11102
|
+
const node = body[i];
|
|
11103
|
+
const blockOutput = await generateOutput(body, i, targetFormat, targetMapper, security, null, true, false, instance, idState);
|
|
11104
|
+
let finalBlockOutput = blockOutput;
|
|
11105
|
+
if (prev_was_silent && node.type === TEXT$1) finalBlockOutput = finalBlockOutput.replace(/^\n/, "");
|
|
11106
|
+
if (finalBlockOutput) {
|
|
11107
|
+
jsOutput += finalBlockOutput;
|
|
11108
|
+
prev_was_silent = false;
|
|
11109
|
+
} else {
|
|
11110
|
+
prev_was_silent = true;
|
|
11111
|
+
}
|
|
11112
|
+
}
|
|
11113
|
+
} finally {
|
|
11114
|
+
Evaluator$1.destroy();
|
|
11115
|
+
}
|
|
11116
|
+
|
|
11117
|
+
return [htmlOutput.trim(), jsOutput.trim()];
|
|
11118
|
+
}
|
|
11119
|
+
|
|
11035
11120
|
try {
|
|
11036
11121
|
for (let i = 0; i < body.length; i++) {
|
|
11037
11122
|
const node = body[i];
|
|
@@ -13733,6 +13818,7 @@ async function resolveModules(ast, context) {
|
|
|
13733
13818
|
security: context.instance.security,
|
|
13734
13819
|
showSpinner: context.instance.showSpinner,
|
|
13735
13820
|
importStack: [...stack, absFilename],
|
|
13821
|
+
moduleIdentityToken: context.instance.moduleIdentityToken,
|
|
13736
13822
|
moduleCache: context.instance.moduleCache
|
|
13737
13823
|
});
|
|
13738
13824
|
|
|
@@ -14095,7 +14181,7 @@ class SomMark {
|
|
|
14095
14181
|
* @param {string} [options.baseDir=null] - The base directory for resolving relative paths.
|
|
14096
14182
|
*/
|
|
14097
14183
|
constructor(options = {}) {
|
|
14098
|
-
const { src, ast = null, format, mapperFile = null, filename = "anonymous", removeComments = true, placeholders = {}, customProps = [], fallbackTarget = "style", outputValidator = null, importAliases = {}, importStack = [], baseDir = null, moduleCache = null, showSpinner = true, security = {}, generateRuntimeOutput = false, hideRuntimeOutput = false, moduleIdentityToken = null } = options;
|
|
14184
|
+
const { src, ast = null, format, mapperFile = null, filename = "anonymous", removeComments = true, placeholders = {}, customProps = [], fallbackTarget = "style", outputValidator = null, importAliases = {}, importStack = [], baseDir = null, moduleCache = null, showSpinner = true, security = {}, generateRuntimeOutput = false, hideRuntimeOutput = false, dualOutput = false, moduleIdentityToken = null } = options;
|
|
14099
14185
|
this.rawSettings = options;
|
|
14100
14186
|
this.src = src;
|
|
14101
14187
|
this.ast = ast;
|
|
@@ -14107,6 +14193,7 @@ class SomMark {
|
|
|
14107
14193
|
this.customProps = customProps;
|
|
14108
14194
|
this.generateRuntimeOutput = generateRuntimeOutput;
|
|
14109
14195
|
this.hideRuntimeOutput = hideRuntimeOutput;
|
|
14196
|
+
this.dualOutput = dualOutput;
|
|
14110
14197
|
this.cwd = options.baseDir || (options.files ? "/" : defaultCwd);
|
|
14111
14198
|
this.fs = options.fs
|
|
14112
14199
|
|| (options.files ? new VirtualFS(options.files) : null)
|
|
@@ -14322,6 +14409,7 @@ class SomMark {
|
|
|
14322
14409
|
settings: this.rawSettings,
|
|
14323
14410
|
generateRuntimeOutput: this.generateRuntimeOutput,
|
|
14324
14411
|
hideRuntimeOutput: this.hideRuntimeOutput,
|
|
14412
|
+
dualOutput: this.dualOutput,
|
|
14325
14413
|
instance: this
|
|
14326
14414
|
});
|
|
14327
14415
|
|
|
@@ -9485,7 +9485,7 @@ function getNodeText$1(node) {
|
|
|
9485
9485
|
* @param {Object} mapper_file - The rules for how to convert each node.
|
|
9486
9486
|
* @returns {Promise<string>} - The final text for this node.
|
|
9487
9487
|
*/
|
|
9488
|
-
async function generateOutput(ast, i, format, mapper_file, security = {}, parentId = null, generateRuntimeOutput = false, hideRuntimeOutput = false, instance = null) {
|
|
9488
|
+
async function generateOutput(ast, i, format, mapper_file, security = {}, parentId = null, generateRuntimeOutput = false, hideRuntimeOutput = false, instance = null, idState = null) {
|
|
9489
9489
|
const node = Array.isArray(ast) ? ast[i] : ast;
|
|
9490
9490
|
if (!node) return "";
|
|
9491
9491
|
|
|
@@ -9500,7 +9500,7 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
|
|
|
9500
9500
|
if (node.body) {
|
|
9501
9501
|
Evaluator.pushScope();
|
|
9502
9502
|
for (let j = 0; j < node.body.length; j++) {
|
|
9503
|
-
bodyOutput += await generateOutput(node.body, j, format, mapper_file, security, parentId, generateRuntimeOutput, hideRuntimeOutput, instance);
|
|
9503
|
+
bodyOutput += await generateOutput(node.body, j, format, mapper_file, security, parentId, generateRuntimeOutput, hideRuntimeOutput, instance, idState);
|
|
9504
9504
|
}
|
|
9505
9505
|
await Evaluator.popScope();
|
|
9506
9506
|
}
|
|
@@ -9608,7 +9608,7 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
|
|
|
9608
9608
|
});
|
|
9609
9609
|
|
|
9610
9610
|
for (let j = 0; j < cleanedBody.length; j++) {
|
|
9611
|
-
output += await generateOutput(cleanedBody, j, format, mapper_file, security, parentId, generateRuntimeOutput, hideRuntimeOutput, instance);
|
|
9611
|
+
output += await generateOutput(cleanedBody, j, format, mapper_file, security, parentId, generateRuntimeOutput, hideRuntimeOutput, instance, idState);
|
|
9612
9612
|
}
|
|
9613
9613
|
|
|
9614
9614
|
await Evaluator.popScope();
|
|
@@ -9631,7 +9631,12 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
|
|
|
9631
9631
|
|
|
9632
9632
|
const hasRuntime = node.body?.some(child => child.type === RUNTIME_LOGIC);
|
|
9633
9633
|
if (hasRuntime) {
|
|
9634
|
-
|
|
9634
|
+
if (idState?.mode === 'replay') {
|
|
9635
|
+
secretId = idState.ids[idState.idx++] ?? `sommark-${node.id.toLowerCase()}-${randomBytesHex(4)}`;
|
|
9636
|
+
} else {
|
|
9637
|
+
secretId = `sommark-${node.id.toLowerCase()}-${randomBytesHex(4)}`;
|
|
9638
|
+
if (idState?.mode === 'record') idState.ids.push(secretId);
|
|
9639
|
+
}
|
|
9635
9640
|
}
|
|
9636
9641
|
}
|
|
9637
9642
|
|
|
@@ -9687,7 +9692,7 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
|
|
|
9687
9692
|
let resolvedBody = "";
|
|
9688
9693
|
Evaluator.pushScope();
|
|
9689
9694
|
for (let j = 0; j < node.body.length; j++) {
|
|
9690
|
-
resolvedBody += await generateOutput(node.body, j, format, mapper_file, security, parentId, generateRuntimeOutput, hideRuntimeOutput, instance);
|
|
9695
|
+
resolvedBody += await generateOutput(node.body, j, format, mapper_file, security, parentId, generateRuntimeOutput, hideRuntimeOutput, instance, idState);
|
|
9691
9696
|
}
|
|
9692
9697
|
await Evaluator.popScope();
|
|
9693
9698
|
content = dedentBy(resolvedBody, node.range?.start?.character || 0);
|
|
@@ -9697,7 +9702,7 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
|
|
|
9697
9702
|
let childrenOutput = "";
|
|
9698
9703
|
if (node.body) {
|
|
9699
9704
|
for (let j = 0; j < node.body.length; j++) {
|
|
9700
|
-
childrenOutput += await generateOutput(node.body, j, format, mapper_file, security, secretId || parentId, generateRuntimeOutput, hideRuntimeOutput, instance);
|
|
9705
|
+
childrenOutput += await generateOutput(node.body, j, format, mapper_file, security, secretId || parentId, generateRuntimeOutput, hideRuntimeOutput, instance, idState);
|
|
9701
9706
|
}
|
|
9702
9707
|
}
|
|
9703
9708
|
return childrenOutput;
|
|
@@ -9808,7 +9813,7 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
|
|
|
9808
9813
|
|
|
9809
9814
|
case FOR_EACH:
|
|
9810
9815
|
case BLOCK:
|
|
9811
|
-
bodyOutput = await generateOutput(body_node, 0, format, mapper_file, security, secretId || parentId, generateRuntimeOutput, hideRuntimeOutput, instance);
|
|
9816
|
+
bodyOutput = await generateOutput(body_node, 0, format, mapper_file, security, secretId || parentId, generateRuntimeOutput, hideRuntimeOutput, instance, idState);
|
|
9812
9817
|
break;
|
|
9813
9818
|
|
|
9814
9819
|
case RUNTIME_LOGIC:
|
|
@@ -9927,6 +9932,31 @@ async function transpiler(optionsOrAst, format, mapperFile) {
|
|
|
9927
9932
|
settings.fs = instance.fs;
|
|
9928
9933
|
}
|
|
9929
9934
|
|
|
9935
|
+
const generateRuntimeOutput = optionsOrAst?.generateRuntimeOutput || false;
|
|
9936
|
+
const hideRuntimeOutput = optionsOrAst?.hideRuntimeOutput || false;
|
|
9937
|
+
const dualOutput = optionsOrAst?.dualOutput || false;
|
|
9938
|
+
|
|
9939
|
+
if (dualOutput && (generateRuntimeOutput || hideRuntimeOutput)) {
|
|
9940
|
+
const flags = [
|
|
9941
|
+
generateRuntimeOutput && "\x1b[36mgenerateRuntimeOutput\x1b[0m",
|
|
9942
|
+
hideRuntimeOutput && "\x1b[36mhideRuntimeOutput\x1b[0m"
|
|
9943
|
+
].filter(Boolean).join(" and ");
|
|
9944
|
+
console.warn(
|
|
9945
|
+
`\n[SomMark] \x1b[33m⚠ Ignored options when dualOutput is true\x1b[0m\n` +
|
|
9946
|
+
` ${flags} ${generateRuntimeOutput && hideRuntimeOutput ? "are" : "is"} ignored when \x1b[32mdualOutput: true\x1b[0m is set.\n` +
|
|
9947
|
+
` \x1b[2mdualOutput manages both HTML and JS passes internally — no need to set those flags.\x1b[0m\n`
|
|
9948
|
+
);
|
|
9949
|
+
} else if (generateRuntimeOutput && hideRuntimeOutput) {
|
|
9950
|
+
console.warn(
|
|
9951
|
+
"\n[SomMark] \x1b[33m⚠ Conflicting options — output will be empty\x1b[0m\n" +
|
|
9952
|
+
" \x1b[36mgenerateRuntimeOutput: true\x1b[0m → outputs only JS, suppresses all HTML\n" +
|
|
9953
|
+
" \x1b[36mhideRuntimeOutput: true\x1b[0m → suppresses all JS output\n" +
|
|
9954
|
+
" Together they cancel each other out and produce nothing.\n" +
|
|
9955
|
+
" \x1b[2mHint: use one at a time, or \x1b[0m\x1b[32mdualOutput: true\x1b[0m\x1b[2m to get [html, js] in one call.\x1b[0m\n"
|
|
9956
|
+
);
|
|
9957
|
+
return "";
|
|
9958
|
+
}
|
|
9959
|
+
|
|
9930
9960
|
// Initialize Logic Sandbox
|
|
9931
9961
|
await Evaluator.init(null, security, settings, targetMapper);
|
|
9932
9962
|
// Inject global data
|
|
@@ -9938,8 +9968,63 @@ async function transpiler(optionsOrAst, format, mapperFile) {
|
|
|
9938
9968
|
let output = "";
|
|
9939
9969
|
let prev_body_node = null;
|
|
9940
9970
|
let prev_was_silent = false;
|
|
9941
|
-
|
|
9942
|
-
|
|
9971
|
+
|
|
9972
|
+
if (dualOutput) {
|
|
9973
|
+
const idState = { mode: 'record', ids: [], idx: 0 };
|
|
9974
|
+
|
|
9975
|
+
// HTML pass — generate HTML, record element IDs for runtime blocks
|
|
9976
|
+
let htmlOutput = "";
|
|
9977
|
+
try {
|
|
9978
|
+
for (let i = 0; i < body.length; i++) {
|
|
9979
|
+
const node = body[i];
|
|
9980
|
+
const blockOutput = await generateOutput(body, i, targetFormat, targetMapper, security, null, false, true, instance, idState);
|
|
9981
|
+
let finalBlockOutput = blockOutput;
|
|
9982
|
+
if (prev_was_silent && node.type === TEXT$1) finalBlockOutput = finalBlockOutput.replace(/^\n/, "");
|
|
9983
|
+
if (finalBlockOutput) {
|
|
9984
|
+
htmlOutput += finalBlockOutput;
|
|
9985
|
+
prev_was_silent = false;
|
|
9986
|
+
} else {
|
|
9987
|
+
prev_was_silent = true;
|
|
9988
|
+
if ((node.type === COMMENT || node.type === COMMENT_BLOCK) && targetMapper?.options?.removeComments) {
|
|
9989
|
+
const nextNode = body[i + 1];
|
|
9990
|
+
if (nextNode && nextNode.type === TEXT$1 && (nextNode.text === "\n" || nextNode.text === "\r\n")) i++;
|
|
9991
|
+
}
|
|
9992
|
+
}
|
|
9993
|
+
}
|
|
9994
|
+
} finally {
|
|
9995
|
+
Evaluator.destroy();
|
|
9996
|
+
}
|
|
9997
|
+
|
|
9998
|
+
// JS pass — replay the same IDs so querySelector targets match HTML
|
|
9999
|
+
idState.mode = 'replay';
|
|
10000
|
+
idState.idx = 0;
|
|
10001
|
+
prev_was_silent = false;
|
|
10002
|
+
|
|
10003
|
+
await Evaluator.init(null, security, settings, targetMapper);
|
|
10004
|
+
Evaluator.inject(placeholders);
|
|
10005
|
+
Evaluator.inject(variables);
|
|
10006
|
+
|
|
10007
|
+
let jsOutput = "";
|
|
10008
|
+
try {
|
|
10009
|
+
for (let i = 0; i < body.length; i++) {
|
|
10010
|
+
const node = body[i];
|
|
10011
|
+
const blockOutput = await generateOutput(body, i, targetFormat, targetMapper, security, null, true, false, instance, idState);
|
|
10012
|
+
let finalBlockOutput = blockOutput;
|
|
10013
|
+
if (prev_was_silent && node.type === TEXT$1) finalBlockOutput = finalBlockOutput.replace(/^\n/, "");
|
|
10014
|
+
if (finalBlockOutput) {
|
|
10015
|
+
jsOutput += finalBlockOutput;
|
|
10016
|
+
prev_was_silent = false;
|
|
10017
|
+
} else {
|
|
10018
|
+
prev_was_silent = true;
|
|
10019
|
+
}
|
|
10020
|
+
}
|
|
10021
|
+
} finally {
|
|
10022
|
+
Evaluator.destroy();
|
|
10023
|
+
}
|
|
10024
|
+
|
|
10025
|
+
return [htmlOutput.trim(), jsOutput.trim()];
|
|
10026
|
+
}
|
|
10027
|
+
|
|
9943
10028
|
try {
|
|
9944
10029
|
for (let i = 0; i < body.length; i++) {
|
|
9945
10030
|
const node = body[i];
|
|
@@ -12641,6 +12726,7 @@ async function resolveModules(ast, context) {
|
|
|
12641
12726
|
security: context.instance.security,
|
|
12642
12727
|
showSpinner: context.instance.showSpinner,
|
|
12643
12728
|
importStack: [...stack, absFilename],
|
|
12729
|
+
moduleIdentityToken: context.instance.moduleIdentityToken,
|
|
12644
12730
|
moduleCache: context.instance.moduleCache
|
|
12645
12731
|
});
|
|
12646
12732
|
|
|
@@ -13003,7 +13089,7 @@ class SomMark {
|
|
|
13003
13089
|
* @param {string} [options.baseDir=null] - The base directory for resolving relative paths.
|
|
13004
13090
|
*/
|
|
13005
13091
|
constructor(options = {}) {
|
|
13006
|
-
const { src, ast = null, format, mapperFile = null, filename = "anonymous", removeComments = true, placeholders = {}, customProps = [], fallbackTarget = "style", outputValidator = null, importAliases = {}, importStack = [], baseDir = null, moduleCache = null, showSpinner = true, security = {}, generateRuntimeOutput = false, hideRuntimeOutput = false, moduleIdentityToken = null } = options;
|
|
13092
|
+
const { src, ast = null, format, mapperFile = null, filename = "anonymous", removeComments = true, placeholders = {}, customProps = [], fallbackTarget = "style", outputValidator = null, importAliases = {}, importStack = [], baseDir = null, moduleCache = null, showSpinner = true, security = {}, generateRuntimeOutput = false, hideRuntimeOutput = false, dualOutput = false, moduleIdentityToken = null } = options;
|
|
13007
13093
|
this.rawSettings = options;
|
|
13008
13094
|
this.src = src;
|
|
13009
13095
|
this.ast = ast;
|
|
@@ -13015,6 +13101,7 @@ class SomMark {
|
|
|
13015
13101
|
this.customProps = customProps;
|
|
13016
13102
|
this.generateRuntimeOutput = generateRuntimeOutput;
|
|
13017
13103
|
this.hideRuntimeOutput = hideRuntimeOutput;
|
|
13104
|
+
this.dualOutput = dualOutput;
|
|
13018
13105
|
this.cwd = options.baseDir || (options.files ? "/" : defaultCwd);
|
|
13019
13106
|
this.fs = options.fs
|
|
13020
13107
|
|| (options.files ? new VirtualFS(options.files) : null)
|
|
@@ -13230,6 +13317,7 @@ class SomMark {
|
|
|
13230
13317
|
settings: this.rawSettings,
|
|
13231
13318
|
generateRuntimeOutput: this.generateRuntimeOutput,
|
|
13232
13319
|
hideRuntimeOutput: this.hideRuntimeOutput,
|
|
13320
|
+
dualOutput: this.dualOutput,
|
|
13233
13321
|
instance: this
|
|
13234
13322
|
});
|
|
13235
13323
|
|
package/index.shared.js
CHANGED
|
@@ -64,7 +64,7 @@ class SomMark {
|
|
|
64
64
|
* @param {string} [options.baseDir=null] - The base directory for resolving relative paths.
|
|
65
65
|
*/
|
|
66
66
|
constructor(options = {}) {
|
|
67
|
-
const { src, ast = null, format, mapperFile = null, filename = "anonymous", removeComments = true, placeholders = {}, customProps = [], fallbackTarget = "style", outputValidator = null, importAliases = {}, importStack = [], baseDir = null, moduleCache = null, showSpinner = true, security = {}, generateRuntimeOutput = false, hideRuntimeOutput = false, moduleIdentityToken = null } = options;
|
|
67
|
+
const { src, ast = null, format, mapperFile = null, filename = "anonymous", removeComments = true, placeholders = {}, customProps = [], fallbackTarget = "style", outputValidator = null, importAliases = {}, importStack = [], baseDir = null, moduleCache = null, showSpinner = true, security = {}, generateRuntimeOutput = false, hideRuntimeOutput = false, dualOutput = false, moduleIdentityToken = null } = options;
|
|
68
68
|
this.rawSettings = options;
|
|
69
69
|
this.src = src;
|
|
70
70
|
this.ast = ast;
|
|
@@ -76,6 +76,7 @@ class SomMark {
|
|
|
76
76
|
this.customProps = customProps;
|
|
77
77
|
this.generateRuntimeOutput = generateRuntimeOutput;
|
|
78
78
|
this.hideRuntimeOutput = hideRuntimeOutput;
|
|
79
|
+
this.dualOutput = dualOutput;
|
|
79
80
|
this.cwd = options.baseDir || (options.files ? "/" : defaultCwd);
|
|
80
81
|
this.fs = options.fs
|
|
81
82
|
|| (options.files ? new VirtualFS(options.files) : null)
|
|
@@ -291,6 +292,7 @@ class SomMark {
|
|
|
291
292
|
settings: this.rawSettings,
|
|
292
293
|
generateRuntimeOutput: this.generateRuntimeOutput,
|
|
293
294
|
hideRuntimeOutput: this.hideRuntimeOutput,
|
|
295
|
+
dualOutput: this.dualOutput,
|
|
294
296
|
instance: this
|
|
295
297
|
});
|
|
296
298
|
|
package/package.json
CHANGED