lilact 0.26.14 → 0.26.16

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 (35) hide show
  1. package/dist/lilact.development.js +528 -291
  2. package/dist/lilact.development.js.map +3 -3
  3. package/dist/lilact.development.min.js +59 -50
  4. package/dist/lilact.development.min.js.map +3 -3
  5. package/dist/lilact.production.min.js +59 -50
  6. package/docs/assets/navigation.js +1 -1
  7. package/docs/assets/search.js +1 -1
  8. package/docs/functions/errors.globalErrorHandler.html +2 -8
  9. package/docs/functions/errors.mapLocation.html +1 -0
  10. package/docs/functions/errors.scanBlockLabels.html +2 -0
  11. package/docs/functions/errors.traceError.html +3 -7
  12. package/docs/functions/run.lazy.html +2 -13
  13. package/docs/functions/run.require.html +2 -9
  14. package/docs/functions/run.run.html +2 -5
  15. package/docs/functions/run.runScripts.html +1 -7
  16. package/docs/functions/timers.timeoutPromise.html +2 -2
  17. package/docs/modules/errors.html +1 -1
  18. package/docs/static/lilact.development.js +528 -291
  19. package/docs/static/lilact.development.js.map +3 -3
  20. package/docs/static/lilact.development.min.js +59 -50
  21. package/docs/static/lilact.development.min.js.map +3 -3
  22. package/docs/static/lilact.production.min.js +59 -50
  23. package/docs/variables/errors.blocks_info.html +1 -0
  24. package/docs/variables/errors.error.html +1 -0
  25. package/examples/lilact.development.js +528 -291
  26. package/examples/lilact.development.js.map +3 -3
  27. package/examples/lilact.development.min.js +59 -50
  28. package/examples/lilact.development.min.js.map +3 -3
  29. package/examples/lilact.production.min.js +59 -50
  30. package/package.json +1 -1
  31. package/scripts/esbuild-preprocessor-plugin.cjs +1 -0
  32. package/src/errors.jsx +525 -237
  33. package/src/jsx.js +4 -2
  34. package/src/lilact.jsx +1 -1
  35. package/src/run.jsx +347 -263
package/src/jsx.js CHANGED
@@ -976,6 +976,7 @@ export function transpileJSX( jsx, {
976
976
  discardComments = false,
977
977
 
978
978
  produceCJS = false,
979
+ logErrors = false,
979
980
 
980
981
  // lilact internal
981
982
  blocks_info = {
@@ -993,10 +994,11 @@ export function transpileJSX( jsx, {
993
994
 
994
995
  raiseError = ((eols, msg, index)=>{
995
996
  const rc = getRowCol(eols, index);
997
+
996
998
  const er = new Error(msg);
997
- console.error(`JSXParserError: ${msg} [file ${path} at line ${rc[0]+1}]`);
999
+ if(logErrors) console.error(`JSXParserError: ${msg} [file ${path} at line ${rc[0]}]`);
998
1000
  [er.lineNumber, er.columnNumber] = rc;
999
- er.name = 'JSXParseError';
1001
+ er.name = 'JSXParserError';
1000
1002
  er.fileName = path;
1001
1003
  er.lilact_trace = 'parse';
1002
1004
  throw er;
package/src/lilact.jsx CHANGED
@@ -149,7 +149,7 @@ document.addEventListener('DOMContentLoaded', () => {
149
149
 
150
150
  if(DEBUG) {
151
151
  window.addEventListener('unhandledrejection', (e) => {
152
- Lilact.globalErrorHandler(e.reason);
152
+ Lilact.globalErrorHandler(e);
153
153
  });
154
154
 
155
155
  window.addEventListener('error', (e) => {
package/src/run.jsx CHANGED
@@ -27,297 +27,381 @@
27
27
  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
28
 
29
29
  */
30
- import Lilact from './lilact.jsx';
31
- import {isEmpty} from './misc.jsx';
30
+ /*
31
+ Lilact
32
+ Copyright (C) 2024-2026 Arash Kazemi
33
+ BSD-2-Clause
34
+ */
32
35
 
33
- import { CORE, COMPONENT, LAZY } from "./symbols.jsx"
34
- import { injectGlobal } from "@emotion/css"
36
+ import Lilact from "./lilact.jsx";
37
+ import { isEmpty } from "./misc.jsx";
38
+ import { LAZY } from "./symbols.jsx";
39
+ import { injectGlobal } from "@emotion/css";
35
40
 
36
41
  function joinPaths(basePath, relativePath) {
37
- const isAbs = relativePath.startsWith("/");
38
- const stack = [];
42
+ const isAbsolute = relativePath.startsWith("/");
43
+ const stack = [];
44
+
45
+ const parts = (isAbsolute ? "" : basePath)
46
+ .split("/")
47
+ .filter(Boolean);
48
+
49
+ for (const part of parts) {
50
+ stack.push(part);
51
+ }
52
+
53
+ if (!basePath.endsWith("/")) {
54
+ stack.pop();
55
+ }
56
+
57
+ for (const part of relativePath.split("/")) {
58
+ if (part === "" || part === ".") continue;
59
+
60
+ if (part === "..") {
61
+ if (stack.length > 0) stack.pop();
62
+ } else {
63
+ stack.push(part);
64
+ }
65
+ }
39
66
 
40
- const parts = (isAbs ? "" : basePath).split("/").filter(Boolean);
41
- for (const p of parts) stack.push(p);
67
+ return `${isAbsolute ? "/" : ""}${stack.join("/")}`;
68
+ }
69
+
70
+ function asError(value, fallbackMessage = "Unknown error") {
71
+ if (value instanceof Error) return value;
72
+
73
+ if (value && typeof value === "object") {
74
+ if (value.error instanceof Error) return value.error;
75
+
76
+ const error = new Error(
77
+ value.message == null ? fallbackMessage : String(value.message)
78
+ );
42
79
 
43
- if(!basePath.endsWith("/")) stack.pop();
80
+ if (value.name) error.name = value.name;
44
81
 
45
- const relParts = relativePath.split("/");
82
+ if (value.stack) {
83
+ Object.defineProperty(error, "stack", {
84
+ value: value.stack,
85
+ configurable: true,
86
+ });
87
+ }
46
88
 
47
- for (const p of relParts) {
48
- if (p === "" || p === ".") continue;
49
- if (p === "..") {
50
- if (stack.length > 0) stack.pop();
51
- } else {
52
- stack.push(p);
53
- }
54
- }
89
+ for (const key of Object.keys(value)) {
90
+ if (!(key in error)) error[key] = value[key];
91
+ }
55
92
 
56
- return (isAbs ? "/" : "") + stack.join("/");
93
+ return error;
94
+ }
95
+
96
+ return new Error(
97
+ value == null ? fallbackMessage : String(value)
98
+ );
57
99
  }
58
100
 
59
- // Examples:
60
- //console.log(joinPaths("a/b/c", "./../d")); // a/b/d
61
- //console.log(joinPaths("a/b/c", "../../d")); // a/d
101
+ function attachPath(error, path) {
102
+ const result = asError(error);
103
+
104
+ if (result.fileName == null) result.fileName = path;
105
+
106
+ return result;
107
+ }
108
+
109
+ function reportRuntimeError(error, path) {
110
+ const withPath = attachPath(error, path);
111
+
112
+ /*
113
+ * lilact.jsx may expose traceError on the public namespace. Avoid a
114
+ * static import here because errors.jsx already imports run.jsx.
115
+ */
116
+ if (typeof Lilact.traceError === "function") {
117
+ return Lilact.traceError(withPath, path);
118
+ }
119
+
120
+ Lilact.error = withPath;
121
+ return withPath;
122
+ }
62
123
 
63
124
  /** @ignore */
64
125
  export const required_scripts = {};
65
126
 
66
-
67
127
  /**
68
- * Runs a jsx script. All scripts can access Lilact namespace as a global object.
69
- *
70
- * @param jsx - The code to run.
71
- * @param path - The optional path to be used in reporting errors.
72
- *
73
- * @returns An array representation of the children.
128
+ * Transpiles and evaluates one JSX module.
74
129
  */
75
- export function run(jsx, path=`InlineJSX-${++Lilact.eval_num}`, {isInline, isModule}={isInline:true, isModule:true})
76
- {
77
- const mappings = [];
78
- const module = {
79
- mappings,
80
- isInline,
81
- path,
82
- code: jsx,
83
- exports: {}
84
- };
85
-
86
- let processed;
87
-
88
-
89
- required_scripts[path] = module;
90
-
91
- try {
92
- processed = Lilact.transpileJSX( jsx,
93
- {
94
- path,
95
- mappings,
96
- factory: "createComponent",
97
- appendSourcemap: false,
98
-
99
- injectTraceLabels: true,
100
- produceCJS: true,
101
-
102
- blocks_info: Lilact.blocks_info,
103
- } );
104
- }
105
- catch(e) {
106
- //e = Lilact.traceError(e);
107
- Lilact.error = e;
108
- throw e;
109
- }
110
-
111
- if(DEBUG) {
112
- required_scripts[path].processed = processed;
113
- }
114
-
115
- processed += "\n//# sourceURL=eval:/" + path;
116
-
117
- // todo: this seems to be only useful in safari, should be assessed later
118
- Lilact.scanBlockLabels(processed, path);
119
-
120
- try {
121
- globalThis.Lilact = Lilact;
122
- globalThis.createComponent = Lilact.createComponent;
123
- globalThis.Fragment = Lilact.Fragment;
124
-
125
- //const res = new Function( "module", processed )(module);
126
- const res = eval(processed);
127
-
128
- if( !isEmpty(module.exports) ) return module.exports;
129
- return res;
130
- }
131
- catch(e) {
132
- e = Lilact.traceError(e, path);
133
- throw e;
134
- }
130
+ export function run(
131
+ jsx,
132
+ path = `InlineJSX-${++Lilact.eval_num}`,
133
+ {
134
+ isInline = true,
135
+ isModule = true,
136
+ } = {}
137
+ ) {
138
+ const mappings = [];
139
+
140
+ const module = {
141
+ mappings,
142
+ isInline,
143
+ isModule,
144
+ path,
145
+ code: String(jsx),
146
+ exports: {},
147
+ };
148
+
149
+ /*
150
+ * Register the module before transpiling. This makes the original source
151
+ * available if transpilation or evaluation fails.
152
+ */
153
+ required_scripts[path] = module;
154
+
155
+ let processed;
156
+
157
+ try {
158
+ processed = Lilact.transpileJSX(String(jsx), {
159
+ path,
160
+ mappings,
161
+ factory: "createComponent",
162
+ appendSourcemap: false,
163
+ injectTraceLabels: true,
164
+ produceCJS: true,
165
+ blocks_info: Lilact.blocks_info,
166
+ });
167
+ } catch (error) {
168
+ const parserError = attachPath(error, path);
169
+ parserError.sourcePhase = "transpile";
170
+ module.error = parserError;
171
+ Lilact.error = parserError;
172
+ throw parserError;
173
+ }
174
+
175
+ if (typeof DEBUG !== "undefined" && DEBUG) {
176
+ module.processed = processed;
177
+ }
178
+
179
+ /*
180
+ * The sourceURL must be the final source directive in the evaluated
181
+ * program. Browsers use it differently, but this format works for the
182
+ * common eval stack formats.
183
+ */
184
+ processed = `${processed}\n//# sourceURL=eval:/${path}`;
185
+
186
+ /*
187
+ * Register block labels using the exact processed source that will be
188
+ * evaluated.
189
+ */
190
+ if (typeof Lilact.scanBlockLabels === "function") {
191
+ Lilact.scanBlockLabels(processed, path);
192
+ }
193
+
194
+ try {
195
+ globalThis.Lilact = Lilact;
196
+ globalThis.createComponent = Lilact.createComponent;
197
+ globalThis.Fragment = Lilact.Fragment;
198
+
199
+ /*
200
+ * This must remain a direct eval. Indirect eval changes the scope and
201
+ * breaks the module runtime.
202
+ */
203
+ const result = eval(processed);
204
+
205
+ if (!isEmpty(module.exports)) {
206
+ return module.exports;
207
+ }
208
+
209
+ return result;
210
+ } catch (error) {
211
+ const runtimeError = reportRuntimeError(error, path);
212
+ runtimeError.sourcePhase = "runtime";
213
+ module.error = runtimeError;
214
+ throw runtimeError;
215
+ }
135
216
  }
136
217
 
137
-
138
218
  /**
139
- * Loads a jsx script from a path. `require` loads synchronously, as it is expected to be loaded on the next instruction.
140
- *
141
- * If the path is in the format #id, it will query the document for a script element with the given
142
- * id and run its contents.
143
- *
144
- * If require is called inside the function given to lazy, it will run async. See `lazy`.
145
- *
146
- * All required scripts can access Lilact namespace as a global object.
147
- *
148
- * @param path - The path to the required file. Must be either absolute path or relative to the current
149
- * module or document’s URL (the page/location that initiated the request).
150
- *
151
- * @returns An array representation of the children.
219
+ * Synchronously or asynchronously loads a JSX, JavaScript, or CSS resource.
152
220
  */
153
- export function require(path)
154
- {
155
- let forceUpdate, checkExport, requirer, isLazy;
156
-
157
- // note: instead of named props, just to bypass typedoc.
158
- if(arguments.length===2 && typeof(arguments[1]==='object')) {
159
- forceUpdate = arguments[1]?.forceUpdate;
160
- checkExport = arguments[1]?.checkExport;
161
- requirer = arguments[1]?.requirer;
162
- isLazy = arguments[1]?.isLazy;
163
- }
164
-
165
- if(Lilact.importObjectPaths?.[path]) return Lilact.importObjectPaths[path];
166
- if(required_scripts[path] && !forceUpdate) return required_scripts[path].exports;
167
-
168
-
169
- if(path[0]==='#') {
170
- const el = document.getElementById(path);
171
-
172
- if(el) {
173
- return run(el.innerText, path);
174
- }
175
-
176
- throw new Error(`Required element not found (${path})`);
177
- }
178
- else {
179
- if(requirer && requirer.path) {
180
- path = joinPaths(requirer.path, path);
181
- }
182
-
183
- if(Lilact?.[LAZY] || isLazy) {
184
- Lilact[LAZY]=false;
185
-
186
- let p = Lilact.resolver?.(path);
187
-
188
- if(p) {
189
- p = Promise.resolve(p);
190
- }
191
- else {
192
- p = fetch(path).then(res => {
193
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
194
- return res.text();
195
- });
196
- }
197
- return p.then(res => {
198
- if(path.endsWith(".css")) {
199
- injectGlobal(res);
200
- return;
201
- }
202
- res = run(res, path, {isInline:false});
203
- return res?.default ?? res;
204
- })
205
- .catch(err => {
206
- throw err;
207
- });
208
- }
209
- else {
210
- const p = Lilact.resolver?.(path);
211
- if(p) {
212
- if(path.endsWith(".css")) {
213
- injectGlobal(p);
214
- return;
215
- }
216
- return run(p, path, {isInline:false});
217
- }
218
- else {
219
- const request = new XMLHttpRequest();
220
- request.open("GET", path, false);
221
- request.send(null);
222
- if (request.status === 200) {
223
- if(path.endsWith(".css")) {
224
- injectGlobal(res);
225
- return;
226
- }
227
- return run(request.responseText, path, {isInline:false});
228
- }
229
- }
230
- }
231
- }
232
-
233
- throw new Error(`Required resource not found (${path})`);
221
+ export function require(path) {
222
+ let forceUpdate;
223
+ let checkExport;
224
+ let requirer;
225
+ let isLazy;
226
+
227
+ if (
228
+ arguments.length === 2 &&
229
+ arguments[1] &&
230
+ typeof arguments[1] === "object"
231
+ ) {
232
+ forceUpdate = arguments[1].forceUpdate;
233
+ checkExport = arguments[1].checkExport;
234
+ requirer = arguments[1].requirer;
235
+ isLazy = arguments[1].isLazy;
236
+ }
237
+
238
+ if (Lilact.importObjectPaths?.[path]) {
239
+ return Lilact.importObjectPaths[path];
240
+ }
241
+
242
+ if (required_scripts[path] && !forceUpdate) {
243
+ return required_scripts[path].exports;
244
+ }
245
+
246
+ if (path[0] === "#") {
247
+ const element = document.getElementById(path.slice(1));
248
+
249
+ if (!element) {
250
+ const error = new Error(
251
+ `Required element not found (${path})`
252
+ );
253
+ error.fileName = path;
254
+ throw error;
255
+ }
256
+
257
+ return run(element.textContent || "", path);
258
+ }
259
+
260
+ if (requirer?.path) {
261
+ path = joinPaths(requirer.path, path);
262
+ }
263
+
264
+ const loadAsync =
265
+ Boolean(Lilact?.[LAZY]) || Boolean(isLazy);
266
+
267
+ if (loadAsync) {
268
+ Lilact[LAZY] = false;
269
+
270
+ let request = Lilact.resolver?.(path);
271
+
272
+ if (request === undefined || request === null) {
273
+ request = fetch(path).then((response) => {
274
+ if (!response.ok) {
275
+ const error = new Error(
276
+ `Unable to load ${path}: HTTP ${response.status}`
277
+ );
278
+ error.fileName = path;
279
+ throw error;
280
+ }
281
+
282
+ return response.text();
283
+ });
284
+ } else {
285
+ request = Promise.resolve(request);
286
+ }
287
+
288
+ return request
289
+ .then((source) => {
290
+ if (path.endsWith(".css")) {
291
+ injectGlobal(String(source));
292
+ return;
293
+ }
294
+
295
+ return run(String(source), path, {
296
+ isInline: false,
297
+ isModule: true,
298
+ });
299
+ })
300
+ .then((result) => {
301
+ if (path.endsWith(".css")) return result;
302
+ return result?.default ?? result;
303
+ })
304
+ .catch((error) => {
305
+ throw reportRuntimeError(error, path);
306
+ });
307
+ }
308
+
309
+ const resolved = Lilact.resolver?.(path);
310
+
311
+ if (resolved !== undefined && resolved !== null) {
312
+ if (path.endsWith(".css")) {
313
+ injectGlobal(String(resolved));
314
+ return;
315
+ }
316
+
317
+ return run(String(resolved), path, {
318
+ isInline: false,
319
+ isModule: true,
320
+ });
321
+ }
322
+
323
+ const request = new XMLHttpRequest();
324
+
325
+ try {
326
+ request.open("GET", path, false);
327
+ request.send(null);
328
+ } catch (error) {
329
+ throw reportRuntimeError(error, path);
330
+ }
331
+
332
+ if (request.status >= 200 && request.status < 300) {
333
+ if (path.endsWith(".css")) {
334
+ injectGlobal(request.responseText);
335
+ return;
336
+ }
337
+
338
+ return run(request.responseText, path, {
339
+ isInline: false,
340
+ isModule: true,
341
+ });
342
+ }
343
+
344
+ const error = new Error(
345
+ `Unable to load ${path}: HTTP ${request.status || 0}`
346
+ );
347
+ error.fileName = path;
348
+ throw error;
234
349
  }
235
350
 
236
-
237
351
  /**
238
- * Wrapper that enables async, code-split component loading. `lazy` should be used
239
- * outside the component definintion or it will produce new components on each rerender.
240
- *
241
- * Note that in factory function you should use require instead of `import`. Dynamic `import`
242
- * would work, but it will not be wired correctly to the `Lilact` runtime.
243
- *
244
- * Example:
245
- * ```
246
- * const StopWatch = lazy( () => require('./stopwatch.jsx') );
247
- * ```
248
- *
249
- * @param factory - A function with **no arguments** that returns a `Promise`.
250
- * The promise must resolve to a module whose module.exports.default is a Lilact component
251
- * or otherwise it will be whatever the module.exports is set to.
252
- *
253
- * @returns A Lilact component that should be rendered inside a `Suspense` boundary.
352
+ * Enables async, code-split component loading.
254
353
  */
255
354
  export function lazy(factory) {
256
- let status = "pending"; // pending | success | error
257
- let result; // component | error
258
-
259
- Lilact[LAZY] = true;
260
- result = factory();
261
-
262
- if(Lilact.isThenable(result)) {
263
- result.then(
264
- (mod) => {
265
- status = "success";
266
- result = mod;
267
- return result;
268
- },
269
- (err) => {
270
- status = "error";
271
- result = err;
272
- throw err;
273
- }
274
- );
275
- }
276
- else {
277
- status = "success";
278
- }
279
-
280
- function LazyComponent(props) {
281
- if (status === "pending") throw result;
282
- if (status === "error") throw result;
283
- const Component = result;
284
- return <Component {...props} />;
285
- }
286
-
287
- return LazyComponent;
355
+ let status = "pending";
356
+ let result;
357
+
358
+ Lilact[LAZY] = true;
359
+
360
+ try {
361
+ result = factory();
362
+ } catch (error) {
363
+ status = "error";
364
+ result = error;
365
+ }
366
+
367
+ if (Lilact.isThenable(result)) {
368
+ result.then(
369
+ (module) => {
370
+ status = "success";
371
+ result = module;
372
+ },
373
+ (error) => {
374
+ status = "error";
375
+ result = error;
376
+ }
377
+ );
378
+ } else if (status !== "error") {
379
+ status = "success";
380
+ }
381
+
382
+ function LazyComponent(props) {
383
+ if (status === "pending") throw result;
384
+ if (status === "error") throw result;
385
+
386
+ const Component = result;
387
+ return <Component {...props} />;
388
+ }
389
+
390
+ return LazyComponent;
288
391
  }
289
392
 
290
393
  function scanScriptTagsWithType() {
291
- const scripts = Array.from(
292
- document.querySelectorAll('script[type="text/jsx"]')
293
- );
294
-
295
- return scripts.map((el) => ({
296
- src: el.getAttribute("src") ?? null,
297
- content: el.textContent ?? ""
298
- }));
394
+ return Array.from(
395
+ document.querySelectorAll('script[type="text/jsx"]')
396
+ ).map((element) => ({
397
+ src: element.getAttribute("src"),
398
+ content: element.textContent || "",
399
+ }));
299
400
  }
300
401
 
301
- /**
302
- * Scans the whole documents and runs all the script elements with type `text/jsx`.
303
- * It is automatically attached to document.onload when Lilact is loaded.
304
- *
305
- * If element src is set, it will be loaded via `require`.
306
- * If element has inner content, it will be executed via `run`.
307
- *
308
- * If both are present, first the src is loaded and then the inner content is executed.
309
- *
310
- * Note that it won't detect such elements that are added after document.onload.
311
- * @returns {void}
312
- */
313
-
314
- export function runScripts()
315
- {
316
- const scripts = scanScriptTagsWithType();
317
-
318
- for(const s of scripts) {
319
- if(s.src) require(s.src);
320
- if(s.content) run(s.content);
321
- }
402
+ export function runScripts() {
403
+ for (const script of scanScriptTagsWithType()) {
404
+ if (script.src) require(script.src);
405
+ if (script.content) run(script.content);
406
+ }
322
407
  }
323
-