lilact 0.6.1 → 0.7.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/src/errors.jsx CHANGED
@@ -31,14 +31,14 @@
31
31
 
32
32
 
33
33
  // Lilact API
34
- /** @ignore */
35
- export function getErrorLocation(err) // works for both error and error-event, and also in node env
34
+
35
+ function getErrorLocation(err) // works for both error and error-event, and also in node env
36
36
  {
37
37
  if(err.lineno!==undefined || err.line!==undefined || err.lineNumber!==undefined) {
38
38
  const l = err.lineNumber || err.lineno || err.line;
39
39
  const c = err.columnNumber || err.colno || err.column;
40
40
 
41
- return [l, c];
41
+ return {line: l, col: c};
42
42
  }
43
43
 
44
44
  let match = /:(\d+):(\d+)[\n].*/m.exec(err.stack);
@@ -47,26 +47,61 @@ export function getErrorLocation(err) // works for both error and error-event, a
47
47
  }
48
48
 
49
49
  if(match) {
50
- return [parseInt(match[1]), parseInt(match[2])]
50
+ return {line: parseInt(match[1]), col: parseInt(match[2])}
51
51
  }
52
52
 
53
53
  return null;
54
54
  }
55
55
 
56
- /** @ignore */
57
- export function mapLocation(mps, r,c)
56
+ // Extract { url, line, col } from an Error.stack.
57
+ // Works with common formats like:
58
+ // Chrome/Edge: at fn (eval:xxx:LINE:COL)
59
+ // Firefox: fn@eval:xxx:LINE:COL
60
+ // Safari: @eval:xxx:LINE:COL (sometimes)
61
+ function parseEvalLocationFromStack(stack, urlPrefix = "eval:/") {
62
+ const raw = typeof stack === "string" ? stack : String(stack || "");
63
+ const lines = raw.split(/\r?\n/);
64
+
65
+ // Match "... (eval:path:LINE:COL)" or "... eval:path:LINE:COL"
66
+ // Group 1: url, group 2: line, group 3: col (col optional in some formats)
67
+ const re = new RegExp(`\\(?((?:${escapeRegExp(urlPrefix)})[^\\s):]+):(\\d+):(?:(\\d+))?\\)?$`);
68
+
69
+ for (const l of lines) {
70
+ const line = l.trim();
71
+ if (!line.includes(urlPrefix)) continue;
72
+
73
+ const m = line.match(re);
74
+ if (!m) continue;
75
+
76
+ const url = m[1];
77
+ const parsedLine = Number(m[2]);
78
+ const parsedCol = m[3] == null ? null : Number(m[3]);
79
+
80
+ if (Number.isFinite(parsedLine) && (parsedCol === null || Number.isFinite(parsedCol))) {
81
+ return { url, line: parsedLine, col: parsedCol, matched: line };
82
+ }
83
+ }
84
+
85
+ return { url: null, line: null, col: null, matched: null, stackPreview: lines.slice(0, 6).join("\n") };
86
+ }
87
+
88
+ function escapeRegExp(s) {
89
+ return String(s).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
90
+ }
91
+
92
+ function mapLocation(mps, r,c)
58
93
  {
59
94
  let map = null;
60
95
 
61
96
  for(const i in mps) {
62
97
  if(mps[i][0]<r) continue;
63
- if(mps[i][0]>r || mps[i][1]>=c) {
98
+ if(mps[i][0]>r || (mps[i][0]===r && mps[i][1]>=c)) {
64
99
  map = mps[i-1];
65
100
  break;
66
101
  }
67
102
  }
68
103
  if(!map) map = mps[mps.length-1];
69
- return [r-map[0]+map[2], ((r-map[0]===0)?map[3]:0)];
104
+ return {line: r-map[0]+map[2], col: ((r-map[0]===0)?map[3]:0) };
70
105
  }
71
106
 
72
107
 
@@ -100,62 +135,70 @@ export function traceError(err)
100
135
  return err;
101
136
  }
102
137
 
103
- const loc = Lilact.getErrorLocation(err);
138
+ const loc = parseEvalLocationFromStack(err.stack);
104
139
 
105
140
  const obj = {
106
- fileName: err.fileName,
107
- label: null,
141
+ fileName: loc.url?.slice(6) || err.fileName,
108
142
 
109
- lineNumber: loc[0],
110
- columnNumber: loc[1],
143
+ lineNumber: loc.line,
144
+ columnNumber: loc.col,
111
145
 
112
146
  message: err.message,
113
147
  name: err.name,
114
148
 
115
- stack: null,
149
+ stack: err.stack,
116
150
  _error: err,
117
151
 
118
152
  is_traced: true
119
153
  };
120
154
 
121
- if( err.name!=='JSXParseError' && err.lilact_trace!==undefined ) {
122
-
123
- // to be able to trace, we assume that all of the scripts are running inside lilact.
124
- // if not, the error is returned unchanged and stack and label would remain null.
155
+ if( err.name!=='JSXParseError' ) {
125
156
 
126
157
  let mps;
127
- let blk;
128
158
 
129
- if(typeof(err.lilact_trace)==='object') {
130
- blk = Lilact.blocks_info.labels[err.lilact_trace[0]];
131
- }
132
- else {
133
- blk = Lilact.blocks_info.labels[err.lilact_trace];
159
+ if(loc.url) {
160
+ const rm = Lilact.required_scripts[obj.fileName];
161
+ mps = rm.mappings;
162
+
163
+ const mloc = mapLocation(mps, obj.lineNumber-1, obj.columnNumber-1);
164
+
165
+ obj.lineNumber = mloc.line;
166
+ obj.columnNumber = mloc.col;
134
167
  }
168
+ else if( err.lilact_trace!==undefined) {
169
+
170
+ let loc = getErrorLocation(err);
171
+
172
+ let mps;
173
+ let blk;
135
174
 
136
- if(blk) {
137
- obj.fileName = blk.path;
138
- obj.label = blk.label;
175
+ if(typeof(err.lilact_trace)==='object') {
176
+ blk = Lilact.blocks_info.labels[err.lilact_trace[0]];
177
+ }
178
+ else {
179
+ blk = Lilact.blocks_info.labels[err.lilact_trace];
180
+ }
139
181
 
140
- mps = Lilact.required_scripts[blk.path].mappings;
182
+ if(blk) {
183
+ obj.fileName = blk.path;
184
+ obj.label = blk.label;
141
185
 
142
- [obj.lineNumber, obj.columnNumber] = Lilact.mapLocation(mps, obj.lineNumber-1,obj.columnNumber-1);
186
+ mps = Lilact.required_scripts[blk.path].mappings;
143
187
 
144
- //const rm = Lilact.block_labels[block_num].required;
188
+ loc = mapLocation(mps, loc.line-1, loc.col-1);
145
189
 
146
- // Lilact.onError( err.message,
147
- // rm.path,
148
- // Lilact.block_labels[block_num].label,
149
- // ...Lilact.mapLocation(...loc,mps),
150
- // Lilact.call_stack.map(
151
- // (x)=>({ path: Lilact.block_labels[x].path,
152
- // label: Lilact.block_labels[x].label,
153
- // lineNumber: Lilact.block_labels[x].lineNumber
154
- // })
155
- // ), err );
190
+ obj.lineNumber = loc.line;
191
+ obj.columnNumber = loc.col;
192
+ }
156
193
  }
157
194
 
158
195
  }
196
+ else {
197
+ const loc = getErrorLocation(err);
198
+ if(err.fileName) obj.fileName = err.fileName;
199
+ obj.lineNumber = loc.line;
200
+ obj.columnNumber = loc.col;
201
+ }
159
202
 
160
203
  Lilact.error = obj;
161
204
  return obj;
@@ -180,9 +223,11 @@ export function traceError(err)
180
223
  */
181
224
  export function globalErrorHandler(err)
182
225
  {
226
+ Lilact.pauseTimers();
183
227
  if(err.error) err = err.error;
184
228
 
185
229
  err = Lilact.traceError(err);
230
+
186
231
  const cls = Lilact.emotion.css(`
187
232
  background: linear-gradient(135deg, #fff2f2d4, #ffffffd4);
188
233
  backdrop-filter: blur(10px);
@@ -199,6 +244,7 @@ export function globalErrorHandler(err)
199
244
  border: 1px solid #0003;
200
245
  overflow: auto;
201
246
  padding: 10px;
247
+ display: block;
202
248
  }
203
249
  `);
204
250
 
@@ -208,25 +254,28 @@ export function globalErrorHandler(err)
208
254
  //<b>⚠</b>
209
255
  el.innerHTML =
210
256
  `<h3 style=""><red>Error!</red></h3>
211
- At <b>${err.fileName}: Line ${err.lineNumber+1}</b><br><br>
257
+ <b>${err.fileName?'At '+err.fileName:''}
258
+ ${Number.isFinite(err.lineNumber)?": Line "+(err.lineNumber+1):""}</b><br><br>
212
259
  <b>${err.name}</b>:&nbsp;<span>${err.message}</span><br><br>
213
- <code><pre></pre><pre><red></red></pre><pre></pre></code>
260
+ ${Lilact.required_scripts[err.fileName]?'<code><pre></pre><pre><red></red></pre><pre></pre></code>':''}
214
261
  ${err._error.componentStackLog?'<br>Component Stack:<br><code><pre>'+err._error.componentStackLog+'</pre></code>':''}
215
262
  `;
216
263
 
217
264
 
218
265
  document.body.appendChild(el);
219
266
 
267
+ const pres = el.querySelectorAll('pre');
220
268
 
221
269
  if(Lilact.required_scripts[err.fileName]) {
222
270
  const lines = Lilact.required_scripts[err.fileName].code.split("\n");
223
- if(err.lineNumber>0)
224
- el.querySelectorAll('pre')[0].innerText = lines[err.lineNumber-1];
225
271
 
226
- el.querySelector('pre red').innerText = lines[err.lineNumber];
272
+ if(lines?.[err.lineNumber-1])
273
+ pres[0].innerText = lines[err.lineNumber-1];
274
+
275
+ if(lines?.[err.lineNumber]) el.querySelector('pre red').innerText = lines[err.lineNumber];
227
276
 
228
- if(err.lineNumber<lines.length-1)
229
- el.querySelectorAll('pre')[2].innerText = lines[err.lineNumber+1];
277
+ if(lines?.[err.lineNumber+1])
278
+ pres[2].innerText = lines[err.lineNumber+1];
230
279
  }
231
280
 
232
281
  el.showModal();
package/src/hooks.jsx CHANGED
@@ -85,7 +85,7 @@ export function useState(val)
85
85
  export function useCallback(callback, deps=undefined)
86
86
  {
87
87
  if( deps!==undefined && (typeof(deps)!=='object' || deps.constructor.name!=='Array') ) {
88
- throw "callback dependencies must be an array or omitted.";
88
+ throw new Error("Callback dependencies must be an array or omitted.");
89
89
  }
90
90
 
91
91
  const hk = Lilact.useHook();
@@ -272,7 +272,7 @@ export function useRef(initialValue = null)
272
272
  export function useLayoutEffect(effect, deps=undefined)
273
273
  {
274
274
  if( deps!==undefined && (typeof(deps)!=='object' || deps.constructor.name!=='Array') ) {
275
- throw "layout effect dependencies must be an array or omitted.";
275
+ throw new Error("Layout effect dependencies must be an array or omitted.");
276
276
  }
277
277
 
278
278
  const hk = Lilact.useHook();
@@ -300,7 +300,7 @@ export function useLayoutEffect(effect, deps=undefined)
300
300
  export function useEffect(effect, deps=undefined)
301
301
  {
302
302
  if( deps!==undefined && (typeof(deps)!=='object' || deps.constructor.name!=='Array') ) {
303
- throw "effect dependencies must be an array or omitted.";
303
+ throw new Error("Effect dependencies must be an array or omitted.");
304
304
  }
305
305
 
306
306
  const hk = Lilact.useHook();
@@ -328,7 +328,7 @@ export function useEffect(effect, deps=undefined)
328
328
  export function useMemo(factory,deps=undefined)
329
329
  {
330
330
  if( deps!==undefined && (typeof(deps)!=='object' || deps.constructor.name!=='Array') ) {
331
- throw "memo dependencies must be an array or omitted.";
331
+ throw new Error("Memo dependencies must be an array or omitted.");
332
332
  }
333
333
 
334
334
  const hk = Lilact.useHook();
package/src/jsx.js CHANGED
@@ -336,9 +336,8 @@ function parseString(code, index, q, container)
336
336
 
337
337
  case '$':
338
338
  if(q==='`') {
339
- index++;
340
- if(code[index]==='{') {
341
- [index] = lookAhead( parseJS, code, index, true, container );
339
+ if(code[index+1]==='{') {
340
+ [index] = lookAhead( parseJS, code, index+1, true, container );
342
341
  index--;
343
342
  }
344
343
  }
@@ -934,7 +933,8 @@ function generateSourceMap(json, path, jsx_eols, out_eols, mappings=[])
934
933
  */
935
934
 
936
935
  export function transpileJSX( jsx, {
937
- factory = "Lilact.createComponent",
936
+ factory = "createComponent",
937
+ fragment = "Fragment",
938
938
  path = "anonymous",
939
939
  appendSourcemap = true,
940
940
  blocks_info = {
@@ -1081,7 +1081,7 @@ export function transpileJSX( jsx, {
1081
1081
  if(node.type==='xml') {
1082
1082
 
1083
1083
  if(node.tag.length===0) {
1084
- node.tag = "null";
1084
+ node.tag = fragment;
1085
1085
  }
1086
1086
  else if(node.tag[0]!==node.tag[0].toUpperCase()) {
1087
1087
  node.tag = `"${node.tag}"`;
package/src/lilact.jsx CHANGED
@@ -64,7 +64,7 @@ import {transpileJSX, transpilerConfig} from "./jsx";
64
64
  export const Lilact =
65
65
  {
66
66
 
67
- VERSION: "beta.6",
67
+ VERSION: "beta.7",
68
68
 
69
69
  // Configuration
70
70
 
package/src/loader.cjs CHANGED
@@ -48,7 +48,7 @@ module.exports = function loader(source) {
48
48
  {
49
49
  path: this.resource,
50
50
  appendSourcemap: DEBUG,
51
- //factory: "Lialct.createComponent",
51
+ //factory: "createComponent",
52
52
  //mappings: [],
53
53
  //injectTraceLabels: false,
54
54
  //discardComments: false
package/src/misc.jsx CHANGED
@@ -78,7 +78,7 @@ export const findDOMNode = (comp)=>{
78
78
 
79
79
  Unlike React, in Lilact findDOMNode can also be used on function components.
80
80
  */
81
- if(!comp[CORE]?.element?.parentNode) throw "findDOMNode only works on mounted components.";
81
+ if(!comp[CORE]?.element?.parentNode) throw new Error("findDOMNode only works on mounted components.");
82
82
  return comp[CORE].element;
83
83
  }
84
84
 
@@ -119,7 +119,7 @@ export const Children = {
119
119
  i--;
120
120
  }
121
121
  if(i>1) {
122
- throw "no child or child is not the only one";
122
+ throw new Error("No child or child is not the only one");
123
123
  }
124
124
  i++;
125
125
  }
@@ -343,7 +343,7 @@ export let update_interval_margin = 0;
343
343
  /** @ignore */
344
344
  export let id_num = Math.floor(Math.random()*10000);
345
345
  /** @ignore */
346
- export let eval_num = Math.floor(Math.random()*10000);
346
+ export let eval_num = 0;//Math.floor(Math.random()*10000);
347
347
 
348
348
 
349
349
  // todo =improve these stacks
package/src/run.jsx CHANGED
@@ -35,12 +35,6 @@ import Lilact from './lilact.jsx';
35
35
 
36
36
  /**
37
37
  * Runs a jsx script. All scripts can access Lilact namespace as a global object.
38
- *
39
- * Notice that `run` uses eval internally. There is no standard way of getting the error location when
40
- * running it with eval. Lilact has its own experimental workarounds for this.
41
- * When an inline script raises an exception, the original exception does not report
42
- * which eval has caused the error, so Lilact should guess it from some labels, and for the sake of
43
- * efficiency, it is block based and not exact.
44
38
  *
45
39
  * @param jsx - The code to run.
46
40
  * @param path - The optional path to be used in reporting errors.
@@ -48,12 +42,13 @@ import Lilact from './lilact.jsx';
48
42
  *
49
43
  * @returns An array representation of the children.
50
44
  */
51
- export function run(jsx, path=`<string input ${++Lilact.eval_num}>`, is_inline=true)
45
+ export function run(jsx, path=`InlineJSX-${++Lilact.eval_num}`, is_inline=true)
52
46
  {
53
47
  const mappings = [];
54
- const module = {};
48
+ const module = {exports: {}};
55
49
  let processed;
56
50
 
51
+
57
52
  Lilact.required_scripts[path] = {
58
53
  mappings,
59
54
  module,
@@ -68,8 +63,8 @@ export function run(jsx, path=`<string input ${++Lilact.eval_num}>`, is_inline=t
68
63
  {
69
64
  path,
70
65
  mappings,
71
- factory: "Lilact.createComponent",
72
- append_sourcemap: !is_inline,
66
+ factory: "createComponent",
67
+ appendSourcemap: false,
73
68
  blocks_info: Lilact.blocks_info,
74
69
 
75
70
  injectTraceLabels: true,
@@ -85,12 +80,18 @@ export function run(jsx, path=`<string input ${++Lilact.eval_num}>`, is_inline=t
85
80
  Lilact.required_scripts[path].processed = processed;
86
81
  ʔ }
87
82
 
83
+ processed += "\n//# sourceURL=eval:/" + path;
84
+
85
+ // todo: this seems to be only useful in safari, should be assessed latera
88
86
  Lilact.scanBlockLabels(processed, path);
89
87
 
90
88
  try {
91
89
  globalThis.Lilact = Lilact;
90
+ globalThis.createComponent = Lilact.createComponent;
91
+ globalThis.Fragment = Lilact.Fragment;
92
92
 
93
- const res = eval ( processed );
93
+ //const res = new Function( "module", processed )(module);
94
+ const res = eval(processed);
94
95
  if(module.exports) return module.exports;
95
96
  return res;
96
97
  }
@@ -106,7 +107,7 @@ export function run(jsx, path=`<string input ${++Lilact.eval_num}>`, is_inline=t
106
107
  *
107
108
  * `Lilact.require` loads synchronously, as it is expected to be loaded on the next instruction.
108
109
  *
109
- * As running the transpiled scripts rely on `eval`, it is not possible to use module imports
110
+ * As running the transpiled scripts rely on `new Function`, it is not possible to use module imports
110
111
  * and exports. So to import you should use `const {useState} = Lilact` convention. And to
111
112
  * export, you should use `module.exports = ...`. `Lilact.require` returns `module.exports` value
112
113
  * so you can import different modules using the convention above.
@@ -125,8 +126,8 @@ export function run(jsx, path=`<string input ${++Lilact.eval_num}>`, is_inline=t
125
126
  */
126
127
  export function require(path, force_update)
127
128
  {
128
- //if(Lilact.transpilerConfig.required[path]!==undefined && !force_update) return Lilact.transpilerConfig.required[path].module;
129
-
129
+ if(Lilact.required_scripts[path] && !force_update) return Lilact.required_scripts[path].module;
130
+
130
131
  if(path[0]==='#') {
131
132
 
132
133
  const el = document.getElementById(path);
@@ -160,13 +161,12 @@ export function require(path, force_update)
160
161
  const request = new XMLHttpRequest();
161
162
  request.open("GET", path, false);
162
163
  request.send(null);
163
-
164
164
  if (request.status === 200) {
165
165
  return Lilact.run(request.responseText, path, false);
166
166
  }
167
167
  }
168
168
 
169
- throw `required resource not found (${path})`;
169
+ throw new Error(`Required resource not found (${path})`);
170
170
  }
171
171
 
172
172