juxscript 1.1.273 → 1.1.275
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/machinery/autowrap.js +190 -0
- package/machinery/compiler4.js +15 -4
- package/package.json +2 -1
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-wraps unwatched pageState references with __watch wrappers.
|
|
3
|
+
*
|
|
4
|
+
* Takes raw .jux source code and returns the same code with
|
|
5
|
+
* reactive statements wrapped in pageState.__watch(() => { ... });
|
|
6
|
+
*
|
|
7
|
+
* Pure function: source in → { code, wrappedCount, details } out
|
|
8
|
+
*/
|
|
9
|
+
import * as acorn from 'acorn';
|
|
10
|
+
|
|
11
|
+
// We use acorn's simple walker inline to avoid import issues
|
|
12
|
+
function walkSimple(node, visitors) {
|
|
13
|
+
if (!node || typeof node !== 'object') return;
|
|
14
|
+
if (Array.isArray(node)) { node.forEach(n => walkSimple(n, visitors)); return; }
|
|
15
|
+
const visitor = visitors[node.type];
|
|
16
|
+
if (visitor) visitor(node);
|
|
17
|
+
for (const key of Object.keys(node)) {
|
|
18
|
+
if (key === 'type' || key === 'start' || key === 'end' || key === 'loc' || key === 'raw') continue;
|
|
19
|
+
const child = node[key];
|
|
20
|
+
if (child && typeof child === 'object') walkSimple(child, visitors);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function isPageStateAccess(node) {
|
|
25
|
+
if (node.type !== 'MemberExpression') return false;
|
|
26
|
+
const obj = node.object;
|
|
27
|
+
if (obj.type === 'MemberExpression' &&
|
|
28
|
+
obj.object.type === 'Identifier' &&
|
|
29
|
+
obj.object.name === 'pageState') return true;
|
|
30
|
+
if (node.object.type === 'Identifier' && node.object.name === 'pageState') return true;
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function containsPageStateRef(node) {
|
|
35
|
+
let found = false;
|
|
36
|
+
walkSimple(node, {
|
|
37
|
+
MemberExpression(n) { if (isPageStateAccess(n)) found = true; }
|
|
38
|
+
});
|
|
39
|
+
return found;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function containsJuxCall(node) {
|
|
43
|
+
let found = false;
|
|
44
|
+
walkSimple(node, {
|
|
45
|
+
CallExpression(n) { if (n.callee?.object?.name === 'jux') found = true; }
|
|
46
|
+
});
|
|
47
|
+
return found;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function getDeclaredVarNames(stmt) {
|
|
51
|
+
if (stmt.type !== 'VariableDeclaration') return [];
|
|
52
|
+
return stmt.declarations
|
|
53
|
+
.filter(d => d.id.type === 'Identifier')
|
|
54
|
+
.map(d => d.id.name);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function usesIdentifier(node, name) {
|
|
58
|
+
let found = false;
|
|
59
|
+
walkSimple(node, {
|
|
60
|
+
Identifier(n) { if (n.name === name) found = true; }
|
|
61
|
+
});
|
|
62
|
+
return found;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function isAlreadyWrapped(stmt) {
|
|
66
|
+
return stmt.type === 'ExpressionStatement' &&
|
|
67
|
+
stmt.expression?.type === 'CallExpression' &&
|
|
68
|
+
stmt.expression?.callee?.object?.name === 'pageState' &&
|
|
69
|
+
stmt.expression?.callee?.property?.name === '__watch';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function isSetupStatement(stmt) {
|
|
73
|
+
if (stmt.type === 'ImportDeclaration') return true;
|
|
74
|
+
if (stmt.type === 'FunctionDeclaration') return true;
|
|
75
|
+
if (stmt.type === 'ExpressionStatement') {
|
|
76
|
+
const expr = stmt.expression;
|
|
77
|
+
if (expr.type === 'CallExpression' &&
|
|
78
|
+
expr.callee?.object?.name === 'jux' &&
|
|
79
|
+
!containsPageStateRef(stmt)) return true;
|
|
80
|
+
if (expr.type === 'AwaitExpression' &&
|
|
81
|
+
expr.argument?.callee?.object?.name === 'jux' &&
|
|
82
|
+
!containsPageStateRef(stmt)) return true;
|
|
83
|
+
}
|
|
84
|
+
if (stmt.type === 'VariableDeclaration' && !containsPageStateRef(stmt)) return true;
|
|
85
|
+
if (stmt.type === 'VariableDeclaration' && containsJuxCall(stmt)) return true;
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function getLineNumber(source, pos) {
|
|
90
|
+
let line = 1;
|
|
91
|
+
for (let i = 0; i < pos; i++) {
|
|
92
|
+
if (source[i] === '\n') line++;
|
|
93
|
+
}
|
|
94
|
+
return line;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @param {string} source - Raw .jux source code
|
|
99
|
+
* @param {string} [filename] - For logging
|
|
100
|
+
* @returns {{ code: string, wrappedCount: number, details: string[] }}
|
|
101
|
+
*/
|
|
102
|
+
export function autowrap(source, filename = '') {
|
|
103
|
+
let ast;
|
|
104
|
+
try {
|
|
105
|
+
ast = acorn.parse(source, {
|
|
106
|
+
ecmaVersion: 2022,
|
|
107
|
+
sourceType: 'module',
|
|
108
|
+
allowAwaitOutsideFunction: true,
|
|
109
|
+
});
|
|
110
|
+
} catch (err) {
|
|
111
|
+
// Can't parse — return as-is
|
|
112
|
+
return { code: source, wrappedCount: 0, details: [`parse error: ${err.message}`] };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const needsWatch = [];
|
|
116
|
+
let i = 0;
|
|
117
|
+
|
|
118
|
+
while (i < ast.body.length) {
|
|
119
|
+
const stmt = ast.body[i];
|
|
120
|
+
|
|
121
|
+
if (isSetupStatement(stmt)) { i++; continue; }
|
|
122
|
+
if (isAlreadyWrapped(stmt)) { i++; continue; }
|
|
123
|
+
|
|
124
|
+
// VariableDeclaration reading pageState — group with next if uses declared var
|
|
125
|
+
if (stmt.type === 'VariableDeclaration' && containsPageStateRef(stmt)) {
|
|
126
|
+
const varNames = getDeclaredVarNames(stmt);
|
|
127
|
+
const groupStmts = [stmt];
|
|
128
|
+
const next = ast.body[i + 1];
|
|
129
|
+
if (next && !isAlreadyWrapped(next) && !isSetupStatement(next)) {
|
|
130
|
+
if (varNames.some(v => usesIdentifier(next, v))) {
|
|
131
|
+
groupStmts.push(next);
|
|
132
|
+
i += 2;
|
|
133
|
+
} else {
|
|
134
|
+
i++;
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
i++;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
needsWatch.push({
|
|
141
|
+
line: getLineNumber(source, groupStmts[0].start),
|
|
142
|
+
endLine: getLineNumber(source, groupStmts[groupStmts.length - 1].end),
|
|
143
|
+
});
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Any other statement referencing pageState
|
|
148
|
+
if (containsPageStateRef(stmt)) {
|
|
149
|
+
needsWatch.push({
|
|
150
|
+
line: getLineNumber(source, stmt.start),
|
|
151
|
+
endLine: getLineNumber(source, stmt.end),
|
|
152
|
+
});
|
|
153
|
+
i++;
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
i++;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (needsWatch.length === 0) {
|
|
161
|
+
return { code: source, wrappedCount: 0, details: [] };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Apply wraps bottom-up to preserve line numbers
|
|
165
|
+
const lines = source.split('\n');
|
|
166
|
+
const sorted = [...needsWatch].sort((a, b) => b.line - a.line);
|
|
167
|
+
const details = [];
|
|
168
|
+
|
|
169
|
+
for (const item of sorted) {
|
|
170
|
+
const startIdx = item.line - 1;
|
|
171
|
+
const endIdx = item.endLine - 1;
|
|
172
|
+
const blockLines = lines.slice(startIdx, endIdx + 1);
|
|
173
|
+
const indent = blockLines[0].match(/^(\s*)/)[1];
|
|
174
|
+
|
|
175
|
+
const wrapped = [
|
|
176
|
+
`${indent}pageState.__watch(() => {`,
|
|
177
|
+
...blockLines.map(l => `${indent} ${l.trim()}`),
|
|
178
|
+
`${indent}});`,
|
|
179
|
+
];
|
|
180
|
+
|
|
181
|
+
lines.splice(startIdx, endIdx - startIdx + 1, ...wrapped);
|
|
182
|
+
details.push(`L${item.line}-${item.endLine}`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
code: lines.join('\n'),
|
|
187
|
+
wrappedCount: needsWatch.length,
|
|
188
|
+
details
|
|
189
|
+
};
|
|
190
|
+
}
|
package/machinery/compiler4.js
CHANGED
|
@@ -5,6 +5,7 @@ import fs from 'fs';
|
|
|
5
5
|
import path from 'path';
|
|
6
6
|
import { fileURLToPath } from 'url';
|
|
7
7
|
import { generateErrorCollector } from './errors.js';
|
|
8
|
+
import { autowrap } from './autowrap.js';
|
|
8
9
|
|
|
9
10
|
const __filename = fileURLToPath(import.meta.url);
|
|
10
11
|
const __dirname = path.dirname(__filename);
|
|
@@ -77,9 +78,19 @@ export class JuxCompiler {
|
|
|
77
78
|
} else if (hasExports) {
|
|
78
79
|
sharedModules.push({ name, file: relativePath, content, originalContent: content });
|
|
79
80
|
} else {
|
|
81
|
+
// ✅ Auto-wrap pageState references before wrapping in async function
|
|
82
|
+
let processedContent = content;
|
|
83
|
+
if (file.endsWith('.jux')) {
|
|
84
|
+
const result = autowrap(content, relativePath);
|
|
85
|
+
if (result.wrappedCount > 0) {
|
|
86
|
+
console.log(`🔄 Auto-wrapped ${result.wrappedCount} reactive block(s) in ${relativePath} [${result.details.join(', ')}]`);
|
|
87
|
+
processedContent = result.code;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
80
91
|
let wrappedContent;
|
|
81
92
|
try {
|
|
82
|
-
const ast = acorn.parse(
|
|
93
|
+
const ast = acorn.parse(processedContent, {
|
|
83
94
|
ecmaVersion: 'latest',
|
|
84
95
|
sourceType: 'module',
|
|
85
96
|
locations: true
|
|
@@ -90,12 +101,12 @@ export class JuxCompiler {
|
|
|
90
101
|
|
|
91
102
|
for (const node of ast.body) {
|
|
92
103
|
if (node.type === 'ImportDeclaration') {
|
|
93
|
-
imports.push(
|
|
104
|
+
imports.push(processedContent.substring(node.start, node.end));
|
|
94
105
|
lastImportEnd = node.end;
|
|
95
106
|
}
|
|
96
107
|
}
|
|
97
108
|
|
|
98
|
-
const restOfCode =
|
|
109
|
+
const restOfCode = processedContent.substring(lastImportEnd).trim();
|
|
99
110
|
|
|
100
111
|
wrappedContent = [
|
|
101
112
|
...imports,
|
|
@@ -107,7 +118,7 @@ export class JuxCompiler {
|
|
|
107
118
|
|
|
108
119
|
} catch (parseError) {
|
|
109
120
|
console.warn(`⚠️ Could not parse ${relativePath}, using basic wrapping`);
|
|
110
|
-
wrappedContent = `export default async function() {\n${
|
|
121
|
+
wrappedContent = `export default async function() {\n${processedContent}\n}`;
|
|
111
122
|
}
|
|
112
123
|
|
|
113
124
|
views.push({ name, file: relativePath, content: wrappedContent, originalContent: content });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "juxscript",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.275",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A JavaScript UX authorship platform",
|
|
6
6
|
"main": "./dist/lib/index.js",
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"import": "./dist/lib/index.js",
|
|
16
16
|
"types": "./dist/lib/index.d.ts"
|
|
17
17
|
},
|
|
18
|
+
"./machinery/*": "./machinery/*",
|
|
18
19
|
"./package.json": "./package.json"
|
|
19
20
|
},
|
|
20
21
|
"files": [
|