lilact 0.26.15 → 0.27.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 (61) hide show
  1. package/dist/lilact.development.js +459 -324
  2. package/dist/lilact.development.js.map +3 -3
  3. package/dist/lilact.development.min.js +63 -54
  4. package/dist/lilact.development.min.js.map +3 -3
  5. package/dist/lilact.production.min.js +63 -54
  6. package/docs/assets/navigation.js +1 -1
  7. package/docs/assets/search.js +1 -1
  8. package/docs/classes/accessories.ErrorBoundary.html +8 -8
  9. package/docs/classes/accessories.Suspense.html +7 -7
  10. package/docs/classes/components.Component.html +11 -11
  11. package/docs/classes/components.HTMLComponent.html +11 -11
  12. package/docs/classes/components.RootComponent.html +11 -11
  13. package/docs/functions/components.cloneComponent.html +1 -1
  14. package/docs/functions/components.createComponent.html +1 -1
  15. package/docs/functions/components.createPortal.html +1 -1
  16. package/docs/functions/components.createRoot.html +1 -1
  17. package/docs/functions/components.memo.html +1 -1
  18. package/docs/functions/components.render.html +1 -1
  19. package/docs/functions/errors.globalErrorHandler.html +1 -8
  20. package/docs/functions/errors.scanBlockLabels.html +1 -0
  21. package/docs/functions/errors.traceError.html +1 -7
  22. package/docs/functions/hooks.startTransition.html +1 -1
  23. package/docs/functions/hooks.useActionState.html +1 -1
  24. package/docs/functions/hooks.useDebugValue.html +1 -1
  25. package/docs/functions/hooks.useDeferredValue.html +1 -1
  26. package/docs/functions/hooks.useEffect.html +1 -1
  27. package/docs/functions/hooks.useImperativeHandle.html +1 -1
  28. package/docs/functions/hooks.useInsertionEffect.html +1 -1
  29. package/docs/functions/hooks.useMemo.html +1 -1
  30. package/docs/functions/hooks.useReducer.html +1 -1
  31. package/docs/functions/run.lazy.html +1 -13
  32. package/docs/functions/run.require.html +1 -9
  33. package/docs/functions/run.run.html +1 -5
  34. package/docs/functions/run.runScripts.html +1 -7
  35. package/docs/functions/timers.timeoutPromise.html +3 -3
  36. package/docs/modules/errors.html +1 -1
  37. package/docs/modules/run.html +1 -1
  38. package/docs/static/index.html +1 -1
  39. package/docs/static/lilact.development.js +459 -324
  40. package/docs/static/lilact.development.js.map +3 -3
  41. package/docs/static/lilact.development.min.js +63 -54
  42. package/docs/static/lilact.development.min.js.map +3 -3
  43. package/docs/static/lilact.production.min.js +63 -54
  44. package/docs/variables/components.cloneElement.html +1 -1
  45. package/docs/variables/errors.blocks_info.html +1 -0
  46. package/docs/variables/errors.error.html +1 -0
  47. package/docs/variables/run.required_scripts.html +1 -0
  48. package/examples/index.html +1 -1
  49. package/examples/lilact.development.js +459 -324
  50. package/examples/lilact.development.js.map +3 -3
  51. package/examples/lilact.development.min.js +63 -54
  52. package/examples/lilact.development.min.js.map +3 -3
  53. package/examples/lilact.production.min.js +63 -54
  54. package/package.json +1 -1
  55. package/src/components.jsx +10 -4
  56. package/src/errors.jsx +344 -245
  57. package/src/expscan.js +1 -1
  58. package/src/hooks.jsx +8 -5
  59. package/src/jsx.js +16 -16
  60. package/src/lilact.jsx +2 -2
  61. package/src/run.jsx +313 -267
package/src/run.jsx CHANGED
@@ -27,297 +27,343 @@
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 <contact.arash.kazemi@gmail.com>
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 absolute = relativePath.startsWith("/");
43
+ const parts = (absolute ? "" : basePath)
44
+ .split("/")
45
+ .filter(Boolean);
39
46
 
40
- const parts = (isAbs ? "" : basePath).split("/").filter(Boolean);
41
- for (const p of parts) stack.push(p);
47
+ if (!absolute && !basePath.endsWith("/")) parts.pop();
42
48
 
43
- if(!basePath.endsWith("/")) stack.pop();
49
+ for (const part of relativePath.split("/")) {
50
+ if (!part || part === ".") continue;
51
+ if (part === "..") parts.pop();
52
+ else parts.push(part);
53
+ }
44
54
 
45
- const relParts = relativePath.split("/");
55
+ return `${absolute ? "/" : ""}${parts.join("/")}`;
56
+ }
46
57
 
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
- }
58
+ function asError(value, fallback = "Unknown error") {
59
+ if (value instanceof Error) return value;
60
+ if (value?.error instanceof Error) return value.error;
55
61
 
56
- return (isAbs ? "/" : "") + stack.join("/");
57
- }
62
+ const error = new Error(
63
+ value?.message == null ? fallback : String(value.message)
64
+ );
58
65
 
59
- // Examples:
60
- //console.log(joinPaths("a/b/c", "./../d")); // a/b/d
61
- //console.log(joinPaths("a/b/c", "../../d")); // a/d
66
+ if (value && typeof value === "object") {
67
+ if (value.name) error.name = value.name;
68
+ if (value.stack) error.stack = value.stack;
62
69
 
63
- /** @ignore */
64
- export const required_scripts = {};
70
+ for (const key of Object.keys(value)) {
71
+ if (!(key in error)) error[key] = value[key];
72
+ }
73
+ }
65
74
 
75
+ return error;
76
+ }
66
77
 
67
- /**
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.
78
+ /*
79
+ * The first module that catches an error owns its source location.
80
+ * Parent modules must never replace it.
74
81
  */
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
- }
135
- }
82
+ function markSource(value, path) {
83
+ const error = asError(value);
136
84
 
85
+ if (!error.lilact_source) {
86
+ error.lilact_source = { path };
87
+ }
137
88
 
138
- /**
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.
152
- */
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})`);
89
+ return error;
234
90
  }
235
91
 
92
+ function report(value, path) {
93
+ const error = markSource(value, path);
236
94
 
237
- /**
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.
254
- */
255
- 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;
95
+ if (error.isTraced) return error;
96
+
97
+ if (typeof Lilact.traceError === "function") {
98
+ return Lilact.traceError(error, path);
99
+ }
100
+
101
+ Lilact.error = error;
102
+ return error;
288
103
  }
289
104
 
290
- function scanScriptTagsWithType() {
291
- const scripts = Array.from(
292
- document.querySelectorAll('script[type="text/jsx"]')
293
- );
105
+ export const required_scripts = {};
294
106
 
295
- return scripts.map((el) => ({
296
- src: el.getAttribute("src") ?? null,
297
- content: el.textContent ?? ""
298
- }));
107
+ export function run(
108
+ jsx,
109
+ path = `InlineJSX-${++Lilact.eval_num}`,
110
+ {
111
+ isInline = true,
112
+ isModule = true,
113
+ } = {}
114
+ ) {
115
+
116
+
117
+ const module = {
118
+ path,
119
+ code: String(jsx),
120
+ mappings: [],
121
+ exports: {},
122
+ isInline,
123
+ isModule,
124
+ };
125
+
126
+ required_scripts[path] = module;
127
+
128
+ let processed;
129
+
130
+ try {
131
+ processed = Lilact.transpileJSX(String(jsx), {
132
+ path,
133
+ mappings: module.mappings,
134
+ factory: "createComponent",
135
+ appendSourcemap: false,
136
+ injectTraceLabels: true,
137
+ produceCJS: true,
138
+ blocks_info: Lilact.blocks_info,
139
+ });
140
+ } catch (value) {
141
+ const error = asError(value);
142
+ error.fileName ??= path;
143
+ error.sourcePhase = "transpile";
144
+ module.error = error;
145
+ Lilact.error = error;
146
+ throw error;
147
+ }
148
+
149
+ if (typeof Lilact.scanBlockLabels === "function") {
150
+ Lilact.scanBlockLabels(processed, path);
151
+ }
152
+
153
+ /*
154
+ * sourceURL helps when the browser includes eval locations in its stack.
155
+ * It is not used as the authoritative source; the catch block below is.
156
+ */
157
+ processed += `\n//# sourceURL=eval:/${path}`;
158
+
159
+ try {
160
+ globalThis.Lilact = Lilact;
161
+ globalThis.createComponent = Lilact.createComponent;
162
+ globalThis.Fragment = Lilact.Fragment;
163
+
164
+ const result = eval(processed);
165
+
166
+ return isEmpty(module.exports)
167
+ ? result
168
+ : module.exports;
169
+ } catch (value) {
170
+ /*
171
+ * This catch executes in the module whose eval failed, including
172
+ * generated-JavaScript syntax errors. Therefore path is authoritative.
173
+ */
174
+ const error = report(value, path);
175
+
176
+ error.sourcePhase ??= "runtime";
177
+ module.error = error;
178
+
179
+ throw error;
180
+ }
299
181
  }
300
182
 
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
- */
183
+ export function require(path) {
184
+ let options = {};
185
+
186
+ if (
187
+ arguments.length === 2 &&
188
+ arguments[1] &&
189
+ typeof arguments[1] === "object"
190
+ ) {
191
+ options = arguments[1];
192
+ }
193
+
194
+
195
+ if (Lilact.importObjectPaths?.[path]) {
196
+ return Lilact.importObjectPaths[path];
197
+ }
198
+
199
+
200
+ const loadAsync =
201
+ Boolean(Lilact[LAZY]) ||
202
+ Boolean(options.isLazy);
203
+
204
+
205
+ if (options.requirer?.path) {
206
+ path = joinPaths(options.requirer.path, path);
207
+ }
208
+
209
+ if (required_scripts[path] && !options.forceUpdate && !loadAsync) {
210
+ return required_scripts[path].exports;
211
+ }
212
+
213
+ if (path.startsWith("#")) {
214
+ const element = document.getElementById(path.slice(1));
215
+
216
+ if (!element) {
217
+ throw report(
218
+ new Error(`Required element not found (${path})`),
219
+ path
220
+ );
221
+ }
222
+
223
+ return run(element.textContent || "", path);
224
+ }
225
+
226
+ if (loadAsync) {
227
+ Lilact[LAZY] = false;
228
+
229
+ let request = Lilact.resolver?.(path);
230
+
231
+ if (request == null) {
232
+
233
+ if (required_scripts[path] && !options.forceUpdate) {
234
+ request = Promise.resolve(required_scripts[path].code);
235
+ }
236
+ else {
237
+ request = fetch(path).then(response => {
238
+ if (!response.ok) {
239
+ throw report(
240
+ new Error(
241
+ `Unable to load ${path}: HTTP ${response.status}`
242
+ ),
243
+ path
244
+ );
245
+ }
246
+
247
+ return response.text();
248
+ });
249
+ }
250
+ }
251
+ else {
252
+ request = Promise.resolve(request);
253
+ }
254
+
255
+ return request
256
+ .then(source => {
257
+ if (path.endsWith(".css")) {
258
+ injectGlobal(String(source));
259
+ return;
260
+ }
261
+
262
+ return run(String(source), path, {
263
+ isInline: false,
264
+ isModule: true,
265
+ });
266
+ })
267
+ .then(result =>
268
+ result?.default ?? result
269
+ )
270
+ .catch(error => {
271
+ throw report(error, path);
272
+ });
273
+ }
274
+
275
+ const resolved = Lilact.resolver?.(path);
276
+
277
+ if (resolved != null) {
278
+ if (path.endsWith(".css")) {
279
+ injectGlobal(String(resolved));
280
+ return;
281
+ }
282
+
283
+ return run(String(resolved), path, {
284
+ isInline: false,
285
+ isModule: true,
286
+ });
287
+ }
288
+
289
+ const request = new XMLHttpRequest();
290
+
291
+ try {
292
+ request.open("GET", path, false);
293
+ request.send(null);
294
+ } catch (value) {
295
+ throw report(value, path);
296
+ }
297
+
298
+ if (request.status >= 200 && request.status < 300) {
299
+ if (path.endsWith(".css")) {
300
+ injectGlobal(request.responseText);
301
+ return;
302
+ }
303
+
304
+ return run(request.responseText, path, {
305
+ isInline: false,
306
+ isModule: true,
307
+ });
308
+ }
309
+
310
+ throw report(
311
+ new Error(`Unable to load ${path}: HTTP ${request.status || 0}`),
312
+ path
313
+ );
314
+ }
313
315
 
314
- export function runScripts()
315
- {
316
- const scripts = scanScriptTagsWithType();
316
+ export function lazy(factory) {
317
+ let status = "pending";
318
+ let result;
319
+
320
+ Lilact[LAZY] = true;
321
+
322
+ try {
323
+ result = factory();
324
+ } catch (error) {
325
+ status = "error";
326
+ result = error;
327
+ }
328
+
329
+ if (Lilact.isThenable(result)) {
330
+ result.then(
331
+ value => {
332
+ status = "success";
333
+ result = value;
334
+ },
335
+ error => {
336
+ status = "error";
337
+ result = error;
338
+ }
339
+ );
340
+ } else if (status !== "error") {
341
+ status = "success";
342
+ }
343
+
344
+ function LazyComponent(props) {
345
+ if (status === "pending") throw result;
346
+ if (status === "error") throw result;
347
+
348
+ const Component = result;
349
+ return <Component {...props} />;
350
+ }
351
+
352
+ return LazyComponent;
353
+ }
317
354
 
318
- for(const s of scripts) {
319
- if(s.src) require(s.src);
320
- if(s.content) run(s.content);
321
- }
355
+ function scriptTags() {
356
+ return Array.from(
357
+ document.querySelectorAll('script[type="text/jsx"]')
358
+ ).map(element => ({
359
+ src: element.getAttribute("src"),
360
+ content: element.textContent || "",
361
+ }));
322
362
  }
323
363
 
364
+ export function runScripts() {
365
+ for (const script of scriptTags()) {
366
+ if (script.src) require(script.src);
367
+ if (script.content) run(script.content);
368
+ }
369
+ }