@cilix/lightjs 0.0.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/README.md +1010 -0
- package/cli.js +163 -0
- package/core.js +2730 -0
- package/index.js +364 -0
- package/package.json +26 -0
package/README.md
ADDED
@@ -0,0 +1,1010 @@
|
|
1
|
+
# Light.js
|
2
|
+
### A new kind of JavaScript framework
|
3
|
+
|
4
|
+
## What is this?
|
5
|
+
|
6
|
+
Light.js is a JavaScript framework in the form of a compiler. Unlike other compiler-oriented JavaScript frameworks, however, Light is used like a proper compiler. It takes .light files, which look like this:
|
7
|
+
|
8
|
+
```javascript
|
9
|
+
html [
|
10
|
+
head []
|
11
|
+
body [
|
12
|
+
h1 [ 'Welcome' ]
|
13
|
+
]
|
14
|
+
]
|
15
|
+
```
|
16
|
+
And compiles them into a single HTML file like this:
|
17
|
+
```html
|
18
|
+
<doctype html>
|
19
|
+
<html>
|
20
|
+
<head></head>
|
21
|
+
<body>
|
22
|
+
<h1>Hello</html>
|
23
|
+
</body>
|
24
|
+
</html>
|
25
|
+
```
|
26
|
+
Using this command:
|
27
|
+
```bash
|
28
|
+
light index.light -o index.html
|
29
|
+
```
|
30
|
+
Or you can use the compiler programatically:
|
31
|
+
```javascript
|
32
|
+
import { light } from 'lightjs';
|
33
|
+
|
34
|
+
const html = await light.compile({
|
35
|
+
input: 'index.light'
|
36
|
+
});
|
37
|
+
```
|
38
|
+
Light.js has components. Here is a counter in Light:
|
39
|
+
```javascript
|
40
|
+
tag Counter [
|
41
|
+
let count = 0
|
42
|
+
|
43
|
+
button on:click='count++' [ 'increment' ]
|
44
|
+
div [ 'current count: {{count}}' ]
|
45
|
+
]
|
46
|
+
|
47
|
+
html [
|
48
|
+
head []
|
49
|
+
body [
|
50
|
+
Counter[]
|
51
|
+
]
|
52
|
+
]
|
53
|
+
```
|
54
|
+
Light.js is **alpha** software. This documentation is a work in progress, but it is comprehensive, and can be given directly to LLMs.
|
55
|
+
|
56
|
+
## Table of Contents
|
57
|
+
|
58
|
+
- [Quick Start](#quick-start)
|
59
|
+
- [Syntax](#syntax)
|
60
|
+
- [Document](#document)
|
61
|
+
- [Data](#data)
|
62
|
+
- [Control Flow](#control-flow)
|
63
|
+
- [Components](#components)
|
64
|
+
- [Javascript](#javascript)
|
65
|
+
- [Usage](#usage)
|
66
|
+
|
67
|
+
## Quick Start
|
68
|
+
|
69
|
+
Create a project boilerplate using Light.js's built-in backend router:
|
70
|
+
|
71
|
+
```bash
|
72
|
+
npx lightjs create my-app
|
73
|
+
```
|
74
|
+
|
75
|
+
Light.js is extremely flexible and can be used with any backend. Use the following command to create the same boilerplate but using Express instead:
|
76
|
+
|
77
|
+
```bash
|
78
|
+
npx lightjs create my-app --express
|
79
|
+
```
|
80
|
+
|
81
|
+
And for the smallest possible boilerplate, use this command:
|
82
|
+
|
83
|
+
```bash
|
84
|
+
npx lightjs create my-app --lean
|
85
|
+
```
|
86
|
+
|
87
|
+
## Syntax
|
88
|
+
|
89
|
+
Light.js syntax is extremely terse but it is **not** whitespace sensitive. It's a much less shift-y version of HTML. You simply drop the angle brackets, and put children between square brackets. Tags without children as well as void tags are ended with an empty set of square brackets.
|
90
|
+
|
91
|
+
```javascript
|
92
|
+
form method='POST' action='/user' [
|
93
|
+
input name='first' []
|
94
|
+
input name='last' []
|
95
|
+
input type='submit' value='submit' []
|
96
|
+
]
|
97
|
+
```
|
98
|
+
|
99
|
+
Textnodes go inside strings.
|
100
|
+
|
101
|
+
```javascript
|
102
|
+
a href='/' [ 'home' ]
|
103
|
+
```
|
104
|
+
|
105
|
+
If a textnode is the only child, the brackets may be omitted.
|
106
|
+
|
107
|
+
```javascript
|
108
|
+
a href='/' 'home'
|
109
|
+
```
|
110
|
+
|
111
|
+
Remember, there is no whitespace sensitivity, so the following is equally valid.
|
112
|
+
|
113
|
+
```javascript
|
114
|
+
a href='/'
|
115
|
+
'home'
|
116
|
+
```
|
117
|
+
|
118
|
+
Light.js supports class shorthand, as seen in other templating languages.
|
119
|
+
|
120
|
+
```javascript
|
121
|
+
a.btn href='/' 'home'
|
122
|
+
```
|
123
|
+
|
124
|
+
Of course, you can use the regular class attribute.
|
125
|
+
|
126
|
+
```javascript
|
127
|
+
a class='btn' href='/' 'home'
|
128
|
+
```
|
129
|
+
|
130
|
+
Light.js supports double-quoted string, single-quoted strings, and backtick strings. All strings may have interpolations.
|
131
|
+
|
132
|
+
```javascript
|
133
|
+
html [
|
134
|
+
head []
|
135
|
+
body [
|
136
|
+
p `
|
137
|
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed nec fermentum arcu.
|
138
|
+
Aliquam vel ullamcorper ipsum. Nullam euismod nisl vel a tristique odio luctus.
|
139
|
+
Integer dapibus nec odio at lacinia. Pellentesque euismod id odio nec hendrerit.
|
140
|
+
`
|
141
|
+
]
|
142
|
+
]
|
143
|
+
```
|
144
|
+
|
145
|
+
Backtick strings give you total whitespace control when using them with whitespace sensitive tags like code or pre:
|
146
|
+
|
147
|
+
```javascript
|
148
|
+
html [
|
149
|
+
head []
|
150
|
+
body [
|
151
|
+
code `
|
152
|
+
const foo = require('foo')
|
153
|
+
|
154
|
+
foo()
|
155
|
+
`
|
156
|
+
]
|
157
|
+
]
|
158
|
+
```
|
159
|
+
|
160
|
+
This prints the following block:
|
161
|
+
|
162
|
+
```
|
163
|
+
const foo = require('foo')
|
164
|
+
|
165
|
+
foo()
|
166
|
+
```
|
167
|
+
|
168
|
+
What's happening here is that the first line is being ignored because it contains only a newline. And the second backtick, the one on the bottom, is controlling how much whitespace is to the left of the code. Look at the next example where the bottom backtick is moved over two spaces:
|
169
|
+
|
170
|
+
```javascript
|
171
|
+
html [
|
172
|
+
head []
|
173
|
+
body [
|
174
|
+
code `
|
175
|
+
const foo = require('foo')
|
176
|
+
|
177
|
+
foo()
|
178
|
+
`
|
179
|
+
]
|
180
|
+
]
|
181
|
+
```
|
182
|
+
|
183
|
+
This produces the following block:
|
184
|
+
|
185
|
+
```
|
186
|
+
const foo = require('foo')
|
187
|
+
|
188
|
+
foo()
|
189
|
+
```
|
190
|
+
|
191
|
+
## Document
|
192
|
+
|
193
|
+
A Light.js document is complete and doesn't require some sort HTML bootloader file. The following is a complete program.
|
194
|
+
|
195
|
+
```javascript
|
196
|
+
html [
|
197
|
+
head []
|
198
|
+
body [
|
199
|
+
h1 "Hello from Light.js"
|
200
|
+
]
|
201
|
+
]
|
202
|
+
```
|
203
|
+
|
204
|
+
## Data
|
205
|
+
|
206
|
+
What follows is not Javascript. It is purely static Light.js data declaration. This is one of the key differences between Light.js and the rest. Light.js's opinion is that the document data, (what React calls "state"), belongs to the markup, and not the Javascript. Light.js sees Javascript as an extension that allows for interactivity.
|
207
|
+
|
208
|
+
```javascript
|
209
|
+
let name = 'Matt'
|
210
|
+
|
211
|
+
html [
|
212
|
+
head []
|
213
|
+
body [
|
214
|
+
h1 "Hello, {{name}}"
|
215
|
+
]
|
216
|
+
]
|
217
|
+
```
|
218
|
+
|
219
|
+
Light.js supports strings, numbers, arrays, objects, boolean, and null, just like JSON.
|
220
|
+
|
221
|
+
```javascript
|
222
|
+
let colors = [
|
223
|
+
'red',
|
224
|
+
'blue',
|
225
|
+
'green'
|
226
|
+
]
|
227
|
+
|
228
|
+
let person = {
|
229
|
+
name: 'Matt',
|
230
|
+
age: 33
|
231
|
+
}
|
232
|
+
|
233
|
+
html [
|
234
|
+
head []
|
235
|
+
body [
|
236
|
+
h1 "Hello, {{person.name}}"
|
237
|
+
]
|
238
|
+
]
|
239
|
+
```
|
240
|
+
|
241
|
+
Light.js supports the following operators:
|
242
|
+
|
243
|
+
```
|
244
|
+
+ - / * % ?:
|
245
|
+
```
|
246
|
+
|
247
|
+
```javascript
|
248
|
+
let count = 4 * (2 + 3)
|
249
|
+
|
250
|
+
let many = count > 10 ? true : false
|
251
|
+
```
|
252
|
+
|
253
|
+
Arrays and objects can be accessed using expressions, as you might expect:
|
254
|
+
|
255
|
+
```javascript
|
256
|
+
let person = {
|
257
|
+
name: 'Matt',
|
258
|
+
age: 33
|
259
|
+
}
|
260
|
+
|
261
|
+
let age = person.age
|
262
|
+
let name = person['nam' + 'e']
|
263
|
+
```
|
264
|
+
|
265
|
+
Expressions can be interpolated into strings using double curly braces.
|
266
|
+
|
267
|
+
```javascript
|
268
|
+
let count = 4 * (2 + 3)
|
269
|
+
|
270
|
+
html [
|
271
|
+
head []
|
272
|
+
body [
|
273
|
+
p '{{ count > 10 ? "many" : "not many" }}'
|
274
|
+
]
|
275
|
+
]
|
276
|
+
```
|
277
|
+
|
278
|
+
Expressions can be passed into attributes using a single set of curly braces.
|
279
|
+
|
280
|
+
```javascript
|
281
|
+
let link = '/'
|
282
|
+
let text = 'home'
|
283
|
+
|
284
|
+
html [
|
285
|
+
head []
|
286
|
+
body [
|
287
|
+
a href={link} '{{text}}'
|
288
|
+
]
|
289
|
+
]
|
290
|
+
```
|
291
|
+
|
292
|
+
Once a Light.js variable is declared it cannot be reassigned, except using Javascript (more on that later). The following is not valid.
|
293
|
+
|
294
|
+
```javascript
|
295
|
+
let name = 'Matt'
|
296
|
+
|
297
|
+
name = 'Bob' // this is illegal
|
298
|
+
|
299
|
+
html [
|
300
|
+
head []
|
301
|
+
body [
|
302
|
+
h1 'Hello {{name}}'
|
303
|
+
]
|
304
|
+
]
|
305
|
+
```
|
306
|
+
|
307
|
+
For data transformations, Light.js has the pipe operator `|`:
|
308
|
+
|
309
|
+
```javascript
|
310
|
+
let name = 'matt' | toupper
|
311
|
+
|
312
|
+
let nums = 1 | repeat 10 // creates an array of 10 1s
|
313
|
+
|
314
|
+
let length = nums | length // 10
|
315
|
+
|
316
|
+
html [
|
317
|
+
head []
|
318
|
+
body [
|
319
|
+
h1 'Hello {{name}}' // Hello MATT
|
320
|
+
]
|
321
|
+
]
|
322
|
+
```
|
323
|
+
|
324
|
+
### Built-in Pipes
|
325
|
+
|
326
|
+
**toupper** - Makes a string uppercase
|
327
|
+
```javascript
|
328
|
+
let name = 'hello' | toupper
|
329
|
+
```
|
330
|
+
|
331
|
+
**tolower** - Makes a string lowercase
|
332
|
+
```javascript
|
333
|
+
let name = 'HELLO' | tolower
|
334
|
+
```
|
335
|
+
|
336
|
+
**length** - Returns the length of the input
|
337
|
+
```javascript
|
338
|
+
let len = x | length
|
339
|
+
```
|
340
|
+
|
341
|
+
**repeat n** - Repeats the input N times. All data types are supported, and will be repeated. Even large complex objects.
|
342
|
+
```javascript
|
343
|
+
let nums = 1 | repeat 10 // 10 1s
|
344
|
+
let x = {} | repeat 100 // 100 empty objects
|
345
|
+
```
|
346
|
+
|
347
|
+
**map x** - Maps an array to an expression. The expression has 2 implicit arguments: `_a`, and `_b` which represent the value and the index respectively. This idea was borrowed from the Lobster programming language.
|
348
|
+
```javascript
|
349
|
+
let people = [{
|
350
|
+
name: 'Frodo'
|
351
|
+
}, {
|
352
|
+
name: 'Sam'
|
353
|
+
}]
|
354
|
+
let names = people | map _a.name
|
355
|
+
// ['Frodo', 'Sam']
|
356
|
+
```
|
357
|
+
|
358
|
+
Here is an example of map being used in conjunction with repeat:
|
359
|
+
```javascript
|
360
|
+
let nums = 1 | repeat 5 | map _a + _b
|
361
|
+
// [1, 2, 3, 4, 5]
|
362
|
+
```
|
363
|
+
|
364
|
+
The equivalent expression in javascript would look like this:
|
365
|
+
```javascript
|
366
|
+
let nums = (new Array(5)).fill(1).map((_a, _b) => _a + _b);
|
367
|
+
```
|
368
|
+
|
369
|
+
**filter x** - Filters an array against an expression that returns true or false. The expression has 2 implicit arguments: `_a`, and `_b` which represent the value and the index respectively.
|
370
|
+
```javascript
|
371
|
+
let nums = 0 | repeat 10 | map _a + _b | filter _b % 2 == 0
|
372
|
+
// [0, 2, 4, 6, 8]
|
373
|
+
```
|
374
|
+
|
375
|
+
**split x** - Splits a string at a given delimeter into an array
|
376
|
+
```javascript
|
377
|
+
let greet = 'helloxworld' | split 'x'
|
378
|
+
// ['hello', 'world']
|
379
|
+
```
|
380
|
+
|
381
|
+
**includes x** - Checks whether or not an item is included in an array
|
382
|
+
```javascript
|
383
|
+
let has4 = [1, 2, 3, 4, 5] | includes 4
|
384
|
+
// true
|
385
|
+
```
|
386
|
+
|
387
|
+
**indexof x** - Retrieves the index of an item in an array. Returns -1 if not found
|
388
|
+
```javascript
|
389
|
+
let pos = [1, 2, 3, 4, 5] | indexof 4
|
390
|
+
// 3
|
391
|
+
```
|
392
|
+
|
393
|
+
**reverse** - Reverses a string or array
|
394
|
+
```javascript
|
395
|
+
let nums = [1, 2, 3] | reverse
|
396
|
+
// [3, 2, 1]
|
397
|
+
let hello = 'hello' | reverse
|
398
|
+
// olleh
|
399
|
+
```
|
400
|
+
|
401
|
+
**tostring** - Converts any data type to a string, including arrays and objects
|
402
|
+
```javascript
|
403
|
+
let person = { name: "matt" } | tostring
|
404
|
+
// '{"name":"matt"}'
|
405
|
+
|
406
|
+
let num = 10 | tostring
|
407
|
+
// '10'
|
408
|
+
```
|
409
|
+
|
410
|
+
**todata** - Converts a string of data into useable data
|
411
|
+
```javascript
|
412
|
+
let person = '{"name":"matt"}' | todata
|
413
|
+
let number = '10' | todata
|
414
|
+
let thetruth = 'true' | todata
|
415
|
+
|
416
|
+
html [
|
417
|
+
head[]
|
418
|
+
body[
|
419
|
+
h1 'Hello {{person.name}}'
|
420
|
+
]
|
421
|
+
]
|
422
|
+
```
|
423
|
+
|
424
|
+
**replace x y** - Replaces the first instance of X in a string with Y
|
425
|
+
```javascript
|
426
|
+
let str = 'hello world' | replace 'd' 'd!'
|
427
|
+
// 'hello world!'
|
428
|
+
```
|
429
|
+
|
430
|
+
**join x** - Joins an array into a string using a delimeter
|
431
|
+
```javascript
|
432
|
+
let x = ['hello', 'world'] | join ' '
|
433
|
+
// 'hello world'
|
434
|
+
```
|
435
|
+
|
436
|
+
**keys** - Create an array from the keys of an object
|
437
|
+
```javascript
|
438
|
+
let person = {
|
439
|
+
name: 'Frodo',
|
440
|
+
age: 80
|
441
|
+
}
|
442
|
+
let keys = person | keys
|
443
|
+
// ['name', 'age']
|
444
|
+
```
|
445
|
+
|
446
|
+
**values** - Create an array from the values of an object
|
447
|
+
```javascript
|
448
|
+
let person = {
|
449
|
+
name: 'Frodo',
|
450
|
+
age: 80
|
451
|
+
}
|
452
|
+
let vals = person | values
|
453
|
+
// ['Frodo', 80]
|
454
|
+
```
|
455
|
+
|
456
|
+
**trim** - Trim the whitespace off the end of a string
|
457
|
+
```javascript
|
458
|
+
let str = ' Hello world ' | trim
|
459
|
+
// 'Hello world'
|
460
|
+
```
|
461
|
+
|
462
|
+
**slice x y** - Create a slice from an array or string
|
463
|
+
```javascript
|
464
|
+
let str = 'Hello world' | slice 0 5
|
465
|
+
// 'Hello'
|
466
|
+
```
|
467
|
+
|
468
|
+
**rand x** - Create a random floating point value between 0 and x
|
469
|
+
```javascript
|
470
|
+
let val = 100 | rand
|
471
|
+
// 85.312
|
472
|
+
```
|
473
|
+
|
474
|
+
### Math Pipes
|
475
|
+
|
476
|
+
Light.js supports the following math pipes: `ceil`, `floor`, `sin`, `cos`, `tan`, `sqrt`
|
477
|
+
|
478
|
+
```javascript
|
479
|
+
let v0 = 1 | sin
|
480
|
+
let v1 = 1 | cos
|
481
|
+
let v2 = 1 | tan
|
482
|
+
|
483
|
+
let v3 = 100 | sqrt
|
484
|
+
let v4 = 3.5 | ceil
|
485
|
+
let v5 = 4.5 | floor
|
486
|
+
|
487
|
+
let v6 = 100 | rand | floor
|
488
|
+
// In javascript, this would be equivalent to
|
489
|
+
// Math.floor(Math.random() * 100)
|
490
|
+
```
|
491
|
+
|
492
|
+
## Control Flow
|
493
|
+
|
494
|
+
In Light.js, there are 3 control flow keywords: `if`, `else`, `each`
|
495
|
+
|
496
|
+
If statements evaluate a single expression. Brackets are used for control flow just like tag children.
|
497
|
+
|
498
|
+
```javascript
|
499
|
+
let num = 10
|
500
|
+
|
501
|
+
html [
|
502
|
+
head []
|
503
|
+
body [
|
504
|
+
if (num > 10) [
|
505
|
+
p 'Number is greater than 10'
|
506
|
+
] else if (num > 5) [
|
507
|
+
p 'Number is greater than 5'
|
508
|
+
] else [
|
509
|
+
p 'Number is less than or equal to 5'
|
510
|
+
]
|
511
|
+
]
|
512
|
+
]
|
513
|
+
```
|
514
|
+
|
515
|
+
Each statements iterate over arrays and objects.
|
516
|
+
|
517
|
+
```javascript
|
518
|
+
let num = [1, 2, 3, 4, 5]
|
519
|
+
|
520
|
+
html [
|
521
|
+
head []
|
522
|
+
body [
|
523
|
+
// the i is optional, and is the index of the item
|
524
|
+
each (n, i in nums) [
|
525
|
+
p 'value: {{n}}'
|
526
|
+
p 'index: {{i}}'
|
527
|
+
]
|
528
|
+
]
|
529
|
+
]
|
530
|
+
```
|
531
|
+
|
532
|
+
With objects, the first and second variables of the each statement are the key and value respectively:
|
533
|
+
|
534
|
+
```javascript
|
535
|
+
let person = {
|
536
|
+
name: 'Frodo',
|
537
|
+
age: 80,
|
538
|
+
location: 'Shire'
|
539
|
+
}
|
540
|
+
|
541
|
+
html [
|
542
|
+
head []
|
543
|
+
body [
|
544
|
+
each (k, v in person) [
|
545
|
+
p '{{k}}: {{v}}'
|
546
|
+
]
|
547
|
+
]
|
548
|
+
]
|
549
|
+
```
|
550
|
+
|
551
|
+
## Components
|
552
|
+
|
553
|
+
You create components in Light.js with the `tag` keyword. Data that is local to a component must be declared within the component.
|
554
|
+
|
555
|
+
```javascript
|
556
|
+
// a component with a single button
|
557
|
+
tag MyButton [
|
558
|
+
let text = 'click'
|
559
|
+
button.btn '{{text}}'
|
560
|
+
]
|
561
|
+
|
562
|
+
html [
|
563
|
+
head[]
|
564
|
+
body[
|
565
|
+
// use like any tag
|
566
|
+
MyButton[]
|
567
|
+
]
|
568
|
+
]
|
569
|
+
```
|
570
|
+
|
571
|
+
Components do not need to have a root element, and can simply be a list of elements:
|
572
|
+
|
573
|
+
```javascript
|
574
|
+
tag MyButtonList [
|
575
|
+
let text = 'click'
|
576
|
+
|
577
|
+
button.btn '{{text}}'
|
578
|
+
button.btn '{{text}}'
|
579
|
+
button.btn '{{text}}'
|
580
|
+
]
|
581
|
+
|
582
|
+
html [
|
583
|
+
head[]
|
584
|
+
body[
|
585
|
+
MyButtonList[]
|
586
|
+
]
|
587
|
+
]
|
588
|
+
```
|
589
|
+
|
590
|
+
Components can be moved to separate files at your discretion. You are free to have single file components or multi-component files. Components in separate files can be imported using the `import` keyword and exported using the `export` keyword:
|
591
|
+
|
592
|
+
```javascript
|
593
|
+
// buttons.lightjs
|
594
|
+
export tag BlueButton [
|
595
|
+
let text = 'click'
|
596
|
+
button.btn-blue '{{text}}'
|
597
|
+
]
|
598
|
+
|
599
|
+
export tag GreenButton [
|
600
|
+
let text = 'click'
|
601
|
+
button.btn-green '{{text}}'
|
602
|
+
]
|
603
|
+
```
|
604
|
+
|
605
|
+
```javascript
|
606
|
+
// index.lightjs
|
607
|
+
// all exports are visible to this file
|
608
|
+
import 'buttons.lightjs'
|
609
|
+
|
610
|
+
html [
|
611
|
+
head[]
|
612
|
+
body[
|
613
|
+
BlueButton[]
|
614
|
+
GreenButton[]
|
615
|
+
]
|
616
|
+
]
|
617
|
+
```
|
618
|
+
|
619
|
+
While simple, this import strategy may cause a name collision. To handle this, an imported module may be namespaced using the 'as' keyword:
|
620
|
+
|
621
|
+
```javascript
|
622
|
+
// index.lightjs
|
623
|
+
import 'buttons.lightjs' as btn
|
624
|
+
|
625
|
+
html [
|
626
|
+
head[]
|
627
|
+
body[
|
628
|
+
btn::BlueButton[]
|
629
|
+
btn::GreenButton[]
|
630
|
+
]
|
631
|
+
]
|
632
|
+
```
|
633
|
+
|
634
|
+
Component props can be accessed using the `props` keyword. Props are passed to components as regular attributes:
|
635
|
+
|
636
|
+
```javascript
|
637
|
+
tag MyButton [
|
638
|
+
button.btn '{{props.text}}'
|
639
|
+
]
|
640
|
+
|
641
|
+
html [
|
642
|
+
head []
|
643
|
+
body [
|
644
|
+
MyButton text='click' []
|
645
|
+
]
|
646
|
+
]
|
647
|
+
```
|
648
|
+
|
649
|
+
Components can have children, and are displayed using the `yield` keyword:
|
650
|
+
|
651
|
+
```javascript
|
652
|
+
tag MySection [
|
653
|
+
div.section-div [
|
654
|
+
yield
|
655
|
+
]
|
656
|
+
]
|
657
|
+
|
658
|
+
html [
|
659
|
+
head []
|
660
|
+
body [
|
661
|
+
MySection [
|
662
|
+
p [
|
663
|
+
'I am a child of .section-div'
|
664
|
+
]
|
665
|
+
]
|
666
|
+
]
|
667
|
+
]
|
668
|
+
```
|
669
|
+
|
670
|
+
Using yield is how common page layouts are achieved in Light.js:
|
671
|
+
|
672
|
+
```javascript
|
673
|
+
// layout.lightjs
|
674
|
+
export tag Layout [
|
675
|
+
html lang='en' [
|
676
|
+
head [
|
677
|
+
title '{{props.title}}'
|
678
|
+
]
|
679
|
+
body [
|
680
|
+
yield
|
681
|
+
]
|
682
|
+
]
|
683
|
+
]
|
684
|
+
```
|
685
|
+
|
686
|
+
```javascript
|
687
|
+
// index.lightjs
|
688
|
+
import 'layout.lightjs'
|
689
|
+
|
690
|
+
Layout title='My Blog' [
|
691
|
+
main [
|
692
|
+
h1 "Welcome!"
|
693
|
+
]
|
694
|
+
]
|
695
|
+
```
|
696
|
+
|
697
|
+
## Javascript
|
698
|
+
|
699
|
+
Light.js documents can be made interactive by adding Javascript. This requires no additional setup. All Javascript in Light.js goes between sets of dashes `---`, or in event listener strings. The `on:` directive is used to attach event listeners to elements.
|
700
|
+
|
701
|
+
```javascript
|
702
|
+
tag Counter [
|
703
|
+
// remember, this is not Javascript
|
704
|
+
let count = 0
|
705
|
+
|
706
|
+
// this part is Javascript
|
707
|
+
// Javascript in Light.js has the power to update template data directly
|
708
|
+
---
|
709
|
+
function increment() {
|
710
|
+
count++
|
711
|
+
}
|
712
|
+
---
|
713
|
+
|
714
|
+
// any valid browser event can follow the on: directive
|
715
|
+
// the string following 'on:click' is also javascript
|
716
|
+
button on:click='increment()' 'click count: {{count}}'
|
717
|
+
]
|
718
|
+
|
719
|
+
html [
|
720
|
+
head []
|
721
|
+
body [
|
722
|
+
Counter[]
|
723
|
+
]
|
724
|
+
]
|
725
|
+
```
|
726
|
+
|
727
|
+
From within Javascript, there are 3 functions and 1 object to know about: `$on`, `$emit`, `$sync` and `$e`. And that's all. The Javascript experience in Light.js doesn't require complex concepts and implicit contexts to learn about. It is simply contextualized properly so that you have access to your Light.js data and the ability to update the document.
|
728
|
+
|
729
|
+
### `$sync()`
|
730
|
+
|
731
|
+
This is the most important function call in your Javascript. This call updates the UI when your data changes. This call is analgous to setState in React, except that it's synchronous and takes no arguments. `$sync()` does NOT need to be called after event listeners, because it is called automatically.
|
732
|
+
|
733
|
+
```javascript
|
734
|
+
// in this example, a counter is incremented every second
|
735
|
+
let count = 0
|
736
|
+
---
|
737
|
+
setInterval(() => {
|
738
|
+
count++
|
739
|
+
// sync needs to be called because this was not triggered by an event listener
|
740
|
+
$sync()
|
741
|
+
}, 1000)
|
742
|
+
---
|
743
|
+
html [
|
744
|
+
head []
|
745
|
+
body [
|
746
|
+
h1 "Count: {{count}}"
|
747
|
+
]
|
748
|
+
]
|
749
|
+
```
|
750
|
+
|
751
|
+
`$sync()` does not need to be called in the following example because increment() was called in an event listener.
|
752
|
+
|
753
|
+
```javascript
|
754
|
+
tag Counter [
|
755
|
+
let count = 0
|
756
|
+
---
|
757
|
+
function increment() {
|
758
|
+
count++
|
759
|
+
}
|
760
|
+
---
|
761
|
+
button on:click='increment()' 'click count: {{count}}'
|
762
|
+
]
|
763
|
+
|
764
|
+
html [
|
765
|
+
head[]
|
766
|
+
body[
|
767
|
+
Counter[]
|
768
|
+
]
|
769
|
+
]
|
770
|
+
```
|
771
|
+
|
772
|
+
### `$e`
|
773
|
+
|
774
|
+
This represents the event object within an event listener. There is nothing else to know about it. It is only available from within the event listener string.
|
775
|
+
|
776
|
+
```javascript
|
777
|
+
let name = 'Matt'
|
778
|
+
|
779
|
+
---
|
780
|
+
function setInput(e) {
|
781
|
+
name = e.target.value
|
782
|
+
}
|
783
|
+
---
|
784
|
+
|
785
|
+
html [
|
786
|
+
head []
|
787
|
+
body [
|
788
|
+
input on:input='setInput($e)' []
|
789
|
+
h1 'Hello {{name}}'
|
790
|
+
]
|
791
|
+
]
|
792
|
+
```
|
793
|
+
|
794
|
+
### `$on(event, callback)`
|
795
|
+
|
796
|
+
This function call allows you to listen for lifecycle events within components. The current list of events is: `mount`, `unmount`, `change`, `render`.
|
797
|
+
|
798
|
+
```javascript
|
799
|
+
tag MyComponent [
|
800
|
+
---
|
801
|
+
$on('mount', () => {
|
802
|
+
console.log('mounted')
|
803
|
+
})
|
804
|
+
|
805
|
+
$on('unmount', () => {
|
806
|
+
console.log('unmounted')
|
807
|
+
})
|
808
|
+
|
809
|
+
$on('change', (oldProps) => {
|
810
|
+
console.log('props are different than last time')
|
811
|
+
})
|
812
|
+
|
813
|
+
$on('prerender', () => {
|
814
|
+
console.log('right before rendering')
|
815
|
+
})
|
816
|
+
|
817
|
+
$on('render', () => {
|
818
|
+
console.log('finished rendering')
|
819
|
+
})
|
820
|
+
---
|
821
|
+
]
|
822
|
+
|
823
|
+
html [
|
824
|
+
head []
|
825
|
+
body [
|
826
|
+
MyComponent[]
|
827
|
+
]
|
828
|
+
]
|
829
|
+
```
|
830
|
+
|
831
|
+
### `$emit(event, data)`
|
832
|
+
|
833
|
+
This function call allows components to emit events that parent components can listen for.
|
834
|
+
|
835
|
+
```javascript
|
836
|
+
tag Child [
|
837
|
+
---
|
838
|
+
function myClick(e) {
|
839
|
+
// emit a synthetic click event, and pass the object along
|
840
|
+
$emit('myclick', e)
|
841
|
+
}
|
842
|
+
---
|
843
|
+
button on:click='myClick($e)' 'click'
|
844
|
+
]
|
845
|
+
|
846
|
+
tag Parent [
|
847
|
+
// You simply use the on: directive on a component to listen for synthetic events
|
848
|
+
Child on:myclick='alert($e)' []
|
849
|
+
]
|
850
|
+
|
851
|
+
html [
|
852
|
+
head []
|
853
|
+
body [
|
854
|
+
Parent[]
|
855
|
+
]
|
856
|
+
]
|
857
|
+
```
|
858
|
+
|
859
|
+
## Usage
|
860
|
+
|
861
|
+
### `lightjs.compile()`
|
862
|
+
|
863
|
+
Fundamentally, Light.js is a library that takes in a Light.js source tree and outputs a single HTML file or string.
|
864
|
+
|
865
|
+
```javascript
|
866
|
+
const lightjs = require('lightjs');
|
867
|
+
|
868
|
+
lightjs.compile({
|
869
|
+
input: 'src/index.lightjs',
|
870
|
+
output: 'public/index.html'
|
871
|
+
});
|
872
|
+
```
|
873
|
+
|
874
|
+
No other step is required for a fully reactive, bundled, minified application.
|
875
|
+
|
876
|
+
This function may also be used to serve dynamic content in real time.
|
877
|
+
|
878
|
+
```javascript
|
879
|
+
const express = require('express');
|
880
|
+
const lightjs = require('lightjs');
|
881
|
+
|
882
|
+
const app = express();
|
883
|
+
|
884
|
+
app.get('/', async (req, res) => {
|
885
|
+
const html = await lightjs.compile({
|
886
|
+
input: 'src/index.lightjs'
|
887
|
+
});
|
888
|
+
res.end(html);
|
889
|
+
});
|
890
|
+
|
891
|
+
app.listen(3838);
|
892
|
+
```
|
893
|
+
|
894
|
+
In order for the above example to have production quality speed, you must enable caching. And if desired, Javascript minification can be enabled as well:
|
895
|
+
|
896
|
+
```javascript
|
897
|
+
const html = await lightjs.compile({
|
898
|
+
input: 'src/index.lightjs',
|
899
|
+
cache: true,
|
900
|
+
minify: true
|
901
|
+
});
|
902
|
+
```
|
903
|
+
|
904
|
+
The last thing to know about Light.js's compile function is document data.
|
905
|
+
|
906
|
+
```javascript
|
907
|
+
const html = await lightjs.compile({
|
908
|
+
input: 'src/about.lightjs',
|
909
|
+
data: {
|
910
|
+
pageTitle: 'About me'
|
911
|
+
}
|
912
|
+
});
|
913
|
+
```
|
914
|
+
|
915
|
+
In your Light.js document pageTitle can be found on the global 'data' object:
|
916
|
+
|
917
|
+
```javascript
|
918
|
+
// index.lightjs
|
919
|
+
html [
|
920
|
+
head []
|
921
|
+
body [
|
922
|
+
h1 '{{data.pageTitle}}'
|
923
|
+
]
|
924
|
+
]
|
925
|
+
```
|
926
|
+
|
927
|
+
### `lightjs.app()`
|
928
|
+
|
929
|
+
As mentioned above, Light.js ships with a built-in backend router. This router is exposed through the 'app' function, which returns a Node.js HTTP request handler.
|
930
|
+
|
931
|
+
```javascript
|
932
|
+
const http = require('http');
|
933
|
+
const light = require('lightjs');
|
934
|
+
|
935
|
+
const PORT = 4477;
|
936
|
+
|
937
|
+
const app = light.app({
|
938
|
+
publicDir: './public',
|
939
|
+
cache: true,
|
940
|
+
minify: true,
|
941
|
+
routes: {
|
942
|
+
// map a route directly to a light file
|
943
|
+
'/': 'src/index.light',
|
944
|
+
|
945
|
+
// or for more complex arrangements, you can map the route to the same arguments you would pass to light.compile()
|
946
|
+
'/blog/:id': {
|
947
|
+
input: 'src/blog.light',
|
948
|
+
data: async (req) => {
|
949
|
+
const post = 'hi' // get blog post with req.params.id
|
950
|
+
return post
|
951
|
+
}
|
952
|
+
}
|
953
|
+
}
|
954
|
+
})
|
955
|
+
|
956
|
+
http.createServer(app).listen(PORT);
|
957
|
+
```
|
958
|
+
|
959
|
+
Light.js's router is also completely compatible with Express:
|
960
|
+
|
961
|
+
```javascript
|
962
|
+
const http = require('http');
|
963
|
+
const light = require('lightjs');
|
964
|
+
const express = require('express');
|
965
|
+
|
966
|
+
const PORT = 4477;
|
967
|
+
|
968
|
+
const app = express();
|
969
|
+
|
970
|
+
const lightRouter = light.app({
|
971
|
+
cache: true,
|
972
|
+
minify: true,
|
973
|
+
routes: {
|
974
|
+
// ...
|
975
|
+
}
|
976
|
+
})
|
977
|
+
|
978
|
+
app.use(express.static('public'));
|
979
|
+
app.use(lightRouter);
|
980
|
+
|
981
|
+
http.createServer(app).listen(PORT);
|
982
|
+
```
|
983
|
+
|
984
|
+
Or, as previously mentioned, you can simply use `light.compile` with Express:
|
985
|
+
|
986
|
+
```javascript
|
987
|
+
const http = require('http');
|
988
|
+
const light = require('lightjs');
|
989
|
+
const express = require('express');
|
990
|
+
|
991
|
+
const PORT = 4477;
|
992
|
+
|
993
|
+
const app = express();
|
994
|
+
|
995
|
+
app.get('/', async (req, res) => {
|
996
|
+
const html = await light.compile({
|
997
|
+
input: 'src/index.light',
|
998
|
+
// for production
|
999
|
+
minify: true,
|
1000
|
+
cache: true
|
1001
|
+
});
|
1002
|
+
|
1003
|
+
res.end(html);
|
1004
|
+
});
|
1005
|
+
|
1006
|
+
http.createServer(app).listen(PORT);
|
1007
|
+
```
|
1008
|
+
|
1009
|
+
This is the entire feature set of Lightjs. If there is even one aspect of Lightjs that is missing from this document, it is considered a bug.
|
1010
|
+
|