@loadbare/app 0.4.0 → 0.5.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.
- package/README.md +53 -82
- package/dist/build/assemble.d.ts +7 -5
- package/dist/build/assemble.d.ts.map +1 -1
- package/dist/build/assemble.js +29 -9
- package/dist/build/cli.d.ts +20 -12
- package/dist/build/cli.d.ts.map +1 -1
- package/dist/build/cli.js +34 -16
- package/dist/build/elements.d.ts +15 -29
- package/dist/build/elements.d.ts.map +1 -1
- package/dist/build/elements.js +25 -111
- package/dist/build/expand.d.ts +1 -1
- package/dist/build/expand.js +1 -1
- package/dist/build/locations.d.ts +14 -37
- package/dist/build/locations.d.ts.map +1 -1
- package/dist/build/locations.js +24 -67
- package/dist/build/origins.d.ts +109 -0
- package/dist/build/origins.d.ts.map +1 -0
- package/dist/build/origins.js +270 -0
- package/dist/core/lb-constants.d.ts +1 -0
- package/dist/core/lb-constants.d.ts.map +1 -1
- package/dist/core/lb-constants.js +15 -8
- package/dist/core/lb-types.d.ts +2 -2
- package/dist/core/lb-types.d.ts.map +1 -1
- package/dist/hub/lb-apply.js +1 -1
- package/dist/hub/lb-hub.d.ts.map +1 -1
- package/dist/hub/lb-hub.js +44 -17
- package/dist/hub/lb-rows.js +3 -3
- package/dist/server/lb-server.d.ts +5 -4
- package/dist/server/lb-server.d.ts.map +1 -1
- package/dist/tests/assemble.test.js +11 -4
- package/dist/tests/elements.test.js +47 -51
- package/dist/tests/expand.test.d.ts +1 -1
- package/dist/tests/expand.test.js +2 -2
- package/dist/tests/fixtures/elements/collision/imports.d.ts +3 -0
- package/dist/tests/fixtures/elements/collision/imports.d.ts.map +1 -0
- package/dist/tests/fixtures/elements/collision/imports.js +1 -0
- package/dist/tests/fixtures/elements/manifest/imports.d.ts +3 -0
- package/dist/tests/fixtures/elements/manifest/imports.d.ts.map +1 -0
- package/dist/tests/fixtures/elements/manifest/imports.js +1 -0
- package/dist/tests/fixtures/elements/manifest-bad-entry/imports.d.ts +3 -0
- package/dist/tests/fixtures/elements/manifest-bad-entry/imports.d.ts.map +1 -0
- package/dist/tests/fixtures/elements/manifest-bad-entry/imports.js +1 -0
- package/dist/tests/fixtures/elements/{collision/elements.d.ts → manifest-not-array/imports.d.ts} +1 -1
- package/dist/tests/fixtures/elements/manifest-not-array/imports.d.ts.map +1 -0
- package/dist/tests/fixtures/elements/manifest-not-array/imports.js +1 -0
- package/dist/tests/fixtures/elements/pkg/acme-widget.d.ts +2 -0
- package/dist/tests/fixtures/elements/pkg/acme-widget.d.ts.map +1 -0
- package/dist/tests/fixtures/elements/pkg/acme-widget.js +1 -0
- package/dist/tests/lb-express.test.js +1 -1
- package/dist/tests/origins.test.d.ts +10 -0
- package/dist/tests/origins.test.d.ts.map +1 -0
- package/dist/tests/origins.test.js +326 -0
- package/dist/tests/pages.test.js +3 -3
- package/dist/tests/styles.test.js +7 -4
- package/docs/reference/builder.md +128 -0
- package/docs/reference/chrome.md +75 -0
- package/docs/reference/css.md +44 -0
- package/docs/reference/custom-elements.md +327 -0
- package/docs/reference/data-binding.md +240 -0
- package/docs/reference/overview.md +38 -0
- package/docs/reference/page-files.md +175 -0
- package/docs/reference/server.md +123 -0
- package/docs/reference/widgets.md +163 -0
- package/docs/roadmap.md +130 -0
- package/docs/testing.md +228 -0
- package/docs/theory.md +344 -223
- package/docs/tutorials/000-getting-started.md +86 -0
- package/docs/tutorials/010-pages-and-navigation.md +129 -0
- package/docs/tutorials/020-css.md +103 -0
- package/docs/tutorials/030-html-decomposition.md +79 -0
- package/docs/tutorials/040-displaying-data.md +169 -0
- package/docs/tutorials/050-actions.md +77 -0
- package/docs/tutorials/060-custom-element-code.md +73 -0
- package/docs/tutorials/065-conditional-rendering.md +161 -0
- package/docs/tutorials/070-displaying-a-list.md +137 -0
- package/docs/tutorials/072-inserting-into-a-list.md +88 -0
- package/docs/tutorials/074-deleting-from-a-list.md +77 -0
- package/docs/tutorials/076-updating-a-list-item.md +86 -0
- package/docs/tutorials/080-widget-requests.md +124 -0
- package/docs/tutorials/090-using-widget-libraries.md +75 -0
- package/package.json +4 -12
- package/dist/client.js +0 -522
- package/dist/demo-static/src/widgets/app-box.d.ts +0 -15
- package/dist/demo-static/src/widgets/app-box.d.ts.map +0 -1
- package/dist/demo-static/src/widgets/app-box.js +0 -19
- package/dist/tests/fixtures/elements/collision/elements.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/collision/elements.js +0 -3
- package/dist/tests/fixtures/elements/manifest/elements.d.ts +0 -5
- package/dist/tests/fixtures/elements/manifest/elements.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/manifest/elements.js +0 -3
- package/dist/tests/fixtures/elements/manifest-bad-tag/elements.d.ts +0 -5
- package/dist/tests/fixtures/elements/manifest-bad-tag/elements.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/manifest-bad-tag/elements.js +0 -3
- package/dist/tests/fixtures/elements/manifest-bad-value/elements.d.ts +0 -5
- package/dist/tests/fixtures/elements/manifest-bad-value/elements.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/manifest-bad-value/elements.js +0 -3
- package/dist/tests/golden.test.d.ts +0 -19
- package/dist/tests/golden.test.d.ts.map +0 -1
- package/dist/tests/golden.test.js +0 -60
- package/dist/tests/helpers/window.d.ts +0 -43
- package/dist/tests/helpers/window.d.ts.map +0 -1
- package/dist/tests/helpers/window.js +0 -78
- package/dist/tests/lb-input.test.d.ts +0 -9
- package/dist/tests/lb-input.test.d.ts.map +0 -1
- package/dist/tests/lb-input.test.js +0 -78
- package/dist/tests/lb-list.test.d.ts +0 -12
- package/dist/tests/lb-list.test.d.ts.map +0 -1
- package/dist/tests/lb-list.test.js +0 -44
- package/dist/tests/lb-options.test.d.ts +0 -10
- package/dist/tests/lb-options.test.d.ts.map +0 -1
- package/dist/tests/lb-options.test.js +0 -121
- package/dist/tests/lb-picker.test.d.ts +0 -14
- package/dist/tests/lb-picker.test.d.ts.map +0 -1
- package/dist/tests/lb-picker.test.js +0 -59
- package/dist/tests/lb-select.test.d.ts +0 -9
- package/dist/tests/lb-select.test.d.ts.map +0 -1
- package/dist/tests/lb-select.test.js +0 -71
- package/dist/tests/lb-table.test.d.ts +0 -15
- package/dist/tests/lb-table.test.d.ts.map +0 -1
- package/dist/tests/lb-table.test.js +0 -205
- package/dist/widgets/index.d.ts +0 -7
- package/dist/widgets/index.d.ts.map +0 -1
- package/dist/widgets/index.js +0 -6
- package/dist/widgets/lb-input.d.ts +0 -2
- package/dist/widgets/lb-input.d.ts.map +0 -1
- package/dist/widgets/lb-input.js +0 -48
- package/dist/widgets/lb-list.d.ts +0 -2
- package/dist/widgets/lb-list.d.ts.map +0 -1
- package/dist/widgets/lb-list.js +0 -17
- package/dist/widgets/lb-options.d.ts +0 -26
- package/dist/widgets/lb-options.d.ts.map +0 -1
- package/dist/widgets/lb-options.js +0 -72
- package/dist/widgets/lb-picker.d.ts +0 -2
- package/dist/widgets/lb-picker.d.ts.map +0 -1
- package/dist/widgets/lb-picker.js +0 -25
- package/dist/widgets/lb-select.d.ts +0 -2
- package/dist/widgets/lb-select.d.ts.map +0 -1
- package/dist/widgets/lb-select.js +0 -43
- package/dist/widgets/lb-table.d.ts +0 -2
- package/dist/widgets/lb-table.d.ts.map +0 -1
- package/dist/widgets/lb-table.js +0 -113
- package/docs/application-chrome.md +0 -36
- package/docs/building-html-pages.md +0 -130
- package/docs/getting-started.md +0 -120
- package/docs/guide.md +0 -1164
- package/docs/hosting.md +0 -218
- package/docs/latent-risks.md +0 -20
- package/widgets/index.ts +0 -6
- package/widgets/lb-input.html +0 -1
- package/widgets/lb-input.ts +0 -64
- package/widgets/lb-list.html +0 -1
- package/widgets/lb-list.ts +0 -21
- package/widgets/lb-options.html +0 -4
- package/widgets/lb-options.ts +0 -88
- package/widgets/lb-picker.html +0 -7
- package/widgets/lb-picker.ts +0 -27
- package/widgets/lb-select.html +0 -4
- package/widgets/lb-select.ts +0 -55
- package/widgets/lb-table.html +0 -8
- package/widgets/lb-table.ts +0 -126
package/docs/theory.md
CHANGED
|
@@ -1,226 +1,347 @@
|
|
|
1
1
|
# Theory of Loadbare App
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
web development: a paradoxical situation in which developer ergonomics appear
|
|
5
|
-
to dominate framework architecture but we end up with developer tools that
|
|
6
|
-
inflict increasing pain and expense at scale. User
|
|
7
|
-
experience, especially speed, is left as "an exercise for the reader" or waved
|
|
8
|
-
away as irrelevant due to "powerful modern hardware."
|
|
9
|
-
|
|
10
|
-
The result is that it is incredibly expensive to make a web app, and the result
|
|
11
|
-
is usually very slow.
|
|
12
|
-
|
|
13
|
-
The iterative effort began with Axiom Zero: The best
|
|
14
|
-
developer experience is creating an application that users appreciate.
|
|
15
|
-
A framework for application development must put user needs first, and
|
|
16
|
-
craft the resulting solution patterns to developer needs after the best
|
|
17
|
-
user result is identified and achieved.
|
|
18
|
-
|
|
19
|
-
So we begin with user experience, a fancy way of saying, "why we built this
|
|
20
|
-
darn app in the first place, and what makes people likely to come back."
|
|
21
|
-
Modern expectations for a web app are numerous and span multiple domains. The
|
|
22
|
-
one domain that seems to have been forgotten as inconvenient to --toy-making--
|
|
23
|
-
developer tool forging is throughput, or performance. Loadbare App therefore puts
|
|
24
|
-
performance first, every decision must result in a performant application.
|
|
25
|
-
|
|
26
|
-
This leads to Axiom 1: Performance drives all architecture. Primary
|
|
27
|
-
decisions drive performance, secondary decisions do not subvert it.
|
|
28
|
-
|
|
29
|
-
## The Performance Budget
|
|
30
|
-
|
|
31
|
-
Research over the past 50 years concludes that users appreciate a tight average
|
|
32
|
-
of ~300ms response time from a request to a completed paint.
|
|
3
|
+
by Ken Downs, August 29, 2026.
|
|
33
4
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
This
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
the
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
5
|
+
Loadbare is one man's answer to the extreme expense and slow performance
|
|
6
|
+
of modern web apps. When using modern frameworks, it seems to me that
|
|
7
|
+
they are composed entirely of accidental complexity, I seem to be always working
|
|
8
|
+
for the tools instead of the other way around.
|
|
9
|
+
|
|
10
|
+
I wanted a new way to develop web apps that reduced accidental complexity
|
|
11
|
+
as far as possible to zero, and gave me snappy performant apps.
|
|
12
|
+
|
|
13
|
+
This essay describes the analysis and conclusions that led to the
|
|
14
|
+
design and implementation of the Loadbare application layer.
|
|
15
|
+
|
|
16
|
+
## The Principle of Developer Experience
|
|
17
|
+
|
|
18
|
+
First I had to find my way out of the cruft that clutters
|
|
19
|
+
the front end discussions of today, and establish a "Principle zero" that
|
|
20
|
+
could be a starting point. This is it:
|
|
21
|
+
|
|
22
|
+
> The Principle of Developer Experience: The best developer experience is
|
|
23
|
+
> creating an application that people use and appreciate.
|
|
24
|
+
|
|
25
|
+
This principle combines and aligns business concerns with engineering
|
|
26
|
+
concerns, grounding any solution in why we do what we do.
|
|
27
|
+
|
|
28
|
+
This principle also
|
|
29
|
+
pre-empts arguments for seemingly elegant abstractions and tooling that
|
|
30
|
+
cannot be obviously and easily shown to contribute directly to an
|
|
31
|
+
application that end users use and appreciate.
|
|
32
|
+
|
|
33
|
+
## The Principle of User Experience
|
|
34
|
+
|
|
35
|
+
The Principle of Developer experience grounds us in the end user,
|
|
36
|
+
and from here I considered my own largest single complaint about
|
|
37
|
+
web apps: they are so damn slow.
|
|
38
|
+
|
|
39
|
+
It seems to me that the front end industry, as represented by the
|
|
40
|
+
fat browser frameworks, has abandoned performance completely.
|
|
41
|
+
|
|
42
|
+
I wanted to put performance front and center, and design around
|
|
43
|
+
it instead of optimizing for it later.
|
|
44
|
+
|
|
45
|
+
After some research into what people perceive as performant, I came
|
|
46
|
+
upon the number of 300ms. Faster than that seems abrupt, slower
|
|
47
|
+
seems sluggish. This led to the next foundation principle:
|
|
48
|
+
|
|
49
|
+
> The Principle of User Experience: a full server round trip should
|
|
50
|
+
> complete, from user action through durable persitence to final
|
|
51
|
+
> paint, in a median time of 300ms.
|
|
52
|
+
|
|
53
|
+
This scopes the goal to performance and performance only. All other
|
|
54
|
+
aspects of the UI are mature, we have a mature inventory of widgets
|
|
55
|
+
for user display and interaction. We do not need to invent new
|
|
56
|
+
solutions for accessibility, tabs, buttons and so forth. What we need
|
|
57
|
+
is a way to use the mature solution and respect the user's time.
|
|
58
|
+
|
|
59
|
+
But we do have a practical implication: if we have a 300ms budget,
|
|
60
|
+
and roughly assume 200ms median wire time, we must split the remaining
|
|
61
|
+
100ms between database, non-db server, and browser. Since the database
|
|
62
|
+
in the limiting case must provide durable persistence, our ideal
|
|
63
|
+
processing time for browser and non-db server activity must
|
|
64
|
+
approach zero. In other words, our solution must be a model citizen,
|
|
65
|
+
chewing up as few cycles as possible while providing the full modern
|
|
66
|
+
experience of a web app, leaving
|
|
67
|
+
as much of the 100ms as possible to the database.
|
|
68
|
+
|
|
69
|
+
## The Primacy of Essential Complexity
|
|
70
|
+
|
|
71
|
+
Now we move on to developer efficiency. Fred Brooks gave us a powerful
|
|
72
|
+
way to consider this, when he made the distinction between "essential difficulties"
|
|
73
|
+
and "accidental difficulties". These terms have since been glossed into
|
|
74
|
+
"essential complexity" and "accidental complexity", and I will
|
|
75
|
+
use the complexity versions to be intelligble to the casual reader.
|
|
76
|
+
|
|
77
|
+
Essential complexity is the inherent difficulty of solving the user's
|
|
78
|
+
problem. This is where we want to spend our time and effort.
|
|
79
|
+
Accidental complexity is the work demanded by the tools we use to solve
|
|
80
|
+
the problem. We want to minimize this to being vanishingly small.
|
|
81
|
+
|
|
82
|
+
Because I believe there is some objective truth to my subjective feeling
|
|
83
|
+
that the fat browser frameworks have become nothing but accidental complexity,
|
|
84
|
+
it seemed necessary to state the princple of the Primacy of
|
|
85
|
+
Essential Complexity, which borrows phrasing from both Brooks and
|
|
86
|
+
Pike to establish that:
|
|
87
|
+
|
|
88
|
+
> Essential Complexity Dominates. Show me your abstractions and tooling and
|
|
89
|
+
> I shall remain mystified. Show me your tables and your visual vocabulary
|
|
90
|
+
> and I shall be enlightened - the organization of the code will be self-evident.
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
## The Search For a Solution
|
|
94
|
+
|
|
95
|
+
The three principles stated above, once articulated, allowed me to
|
|
96
|
+
clarify my rejection of the fat browser frameworks: nowhere do they
|
|
97
|
+
state the same motivations that I have, and their illustrations and
|
|
98
|
+
examples make clear their motivations are irrelevent to mine and
|
|
99
|
+
vice-versa. In fact, my
|
|
100
|
+
extensive experience with React, Angular and VueJS contributed to
|
|
101
|
+
my articulation of these principles, the principles state what I found
|
|
102
|
+
lacking in the fat browser frameworks.
|
|
103
|
+
|
|
104
|
+
This led me to look at the Hypermedia libraries. After several weeks
|
|
105
|
+
of iterations, I found myself frustrated. These were very clearly
|
|
106
|
+
closer to what I wanted, and the various authors' motivations seemed
|
|
107
|
+
very close to my own, but I was still seeing more tooling concerns
|
|
108
|
+
than I wanted. I do not blame the hypermedia libraries, I think I was
|
|
109
|
+
fighting them. But that simply told me we differed on an assumption,
|
|
110
|
+
and that assumption remained unidentified and unstated.
|
|
111
|
+
|
|
112
|
+
It turns out that all of the tools and frameworks I could find are
|
|
113
|
+
supporting something I don't need: a DOM that is fully mutatable at
|
|
114
|
+
any time, for any reason deemed necessary by the developer. They
|
|
115
|
+
all provide or expect an HTML templating system that combines
|
|
116
|
+
interpolation of data values with conditional and list rendering,
|
|
117
|
+
controlled by run-time attributes. All of them must chase down the
|
|
118
|
+
consequences of the decision to allow a fully mutatable DOM. Those
|
|
119
|
+
consequences spill into the usage and tooling around the frameworks
|
|
120
|
+
and libraries. The arbitrarily mutatable DOM was the source of my
|
|
121
|
+
intuitive sense that I was dealing with too much accidental complexity.
|
|
122
|
+
|
|
123
|
+
## The Loadbare Split
|
|
124
|
+
|
|
125
|
+
Finally I had the foundational idea for a framework that splits code
|
|
126
|
+
between a static "host" application and an active data channel, with
|
|
127
|
+
some small framework library updating only data values in the DOM
|
|
128
|
+
by following data-binding attributes in the HTML.
|
|
129
|
+
|
|
130
|
+
(As a side note, I originally considered calling Loadbare "liveload"
|
|
131
|
+
to draw the association to a bridge. The static HTML is the bridge,
|
|
132
|
+
the data that refreshes the page is the "live load" (traffic) travelling
|
|
133
|
+
on it. Alas, it seemed too close to 'livereload' and I gave it up.)
|
|
134
|
+
|
|
135
|
+
The static host application is pure HTML, including custom elements
|
|
136
|
+
with Light DOM. User interactions send requests to the server which
|
|
137
|
+
returns data objects. The HTML contains attributes identifying how
|
|
138
|
+
elements are bound to data, so the framework can update them.
|
|
139
|
+
|
|
140
|
+
This should be self-evidently efficacious for scalar cells and
|
|
141
|
+
tuples. Something like `<span lb-query="siteStats" lb-cell="visitCount"></span>`
|
|
142
|
+
or the same type of addressing for an input makes it trivial to
|
|
143
|
+
both hyrdrate and refresh forms, and to gather a form's values
|
|
144
|
+
to send to the server.
|
|
145
|
+
|
|
146
|
+
List processing, such as for an HTML SELECT and complex tables took a bit
|
|
147
|
+
longer to figure out, but the principle could be extended. Any list
|
|
148
|
+
element can accept a `<template>` that describes how to build its
|
|
149
|
+
items. The data channel must expand to allow "patch" operations,
|
|
150
|
+
removing or adding one row instead of reshipping the entire
|
|
151
|
+
query, but that is also straightforward.
|
|
152
|
+
|
|
153
|
+
So a purist might say, "hey you are still manipulating the DOM".
|
|
154
|
+
Of course we are, it is an application, not a static site. But the
|
|
155
|
+
point is that the HTML can be written, read, and reasoned
|
|
156
|
+
about as a static artefact, while still being dynamic at runtime.
|
|
157
|
+
|
|
158
|
+
The model can also be extended for trees, but my current projects
|
|
159
|
+
do not require this, so it is not implemented as of this writing.
|
|
160
|
+
|
|
161
|
+
## Choosing the Mechanisms
|
|
162
|
+
|
|
163
|
+
So now I had three principles and one architectural decision that
|
|
164
|
+
seemed to make it all possible. The next step was to pick the
|
|
165
|
+
fewest number of mechanisms that would work.
|
|
166
|
+
|
|
167
|
+
On the benefit side, the fewer mechanisms there are the easier
|
|
168
|
+
it would be to create the solution and to use the solution. Fewer
|
|
169
|
+
mechanisms makes for a system that is easier to learn, and easier
|
|
170
|
+
to teach to an LLM.
|
|
171
|
+
|
|
172
|
+
On the cost-avoidance side, each mechanism is
|
|
173
|
+
an invitation to accidental complexity around the use of the
|
|
174
|
+
mechanism, so they must be kept at a minimum.
|
|
175
|
+
|
|
176
|
+
### Browser Application Code
|
|
177
|
+
|
|
178
|
+
Using plain HTML, extended with custom elements that do not use Shadow
|
|
179
|
+
DOM has so far, apparently, completely solved the browser for me.
|
|
180
|
+
|
|
181
|
+
In my humble opinion,
|
|
182
|
+
Javascript belongs in the browser to enhance default behavior, and
|
|
183
|
+
the enhancements ought to always be scoped to a custom element and
|
|
184
|
+
its children. A custom element neatly satisfies this.
|
|
185
|
+
|
|
186
|
+
Custom elements give an nice answer to the question, why can't we just
|
|
187
|
+
use HTML like we used to? The Loadbare answer is: you can, go ahead.
|
|
188
|
+
If you need something special, make a custom element, it is still
|
|
189
|
+
just HTML.
|
|
190
|
+
|
|
191
|
+
A simple builder also gives server-side HTML includes for free with
|
|
192
|
+
no extra syntax. The builder scans for one fixed file (the chrome),
|
|
193
|
+
and all pages. Anywhere a custom element is found, it looks for
|
|
194
|
+
an HTML file to match, and inserts it into the built HTML. This is recursive
|
|
195
|
+
of course.
|
|
196
|
+
|
|
197
|
+
The builder became "tree shaking" for free. During HTML assembly
|
|
198
|
+
and composition, any Typescript file whose name matches a custom
|
|
199
|
+
element that was actually used gets added to the monolithic
|
|
200
|
+
`client.js`. No import statement required. The Loadbare code is
|
|
201
|
+
loading no extra Javascript outside of the single `client.js` named
|
|
202
|
+
in the main HTML document.
|
|
203
|
+
|
|
204
|
+
The validation during a build also fell out for free: if a custom
|
|
205
|
+
element is used, it must have either a matching Typescript file
|
|
206
|
+
or a matching HTML file, or both. The only error is if neither
|
|
207
|
+
are present.
|
|
208
|
+
|
|
209
|
+
### Data Binding
|
|
210
|
+
|
|
211
|
+
For the hub to know what data to apply where and how, we need
|
|
212
|
+
custom attributes on the HTML. It was possible to define a small
|
|
213
|
+
and coherent vocabulary because the actual number of operations
|
|
214
|
+
afforded by a database is small and coherent.
|
|
215
|
+
|
|
216
|
+
### Browser Framework Code
|
|
217
|
+
|
|
218
|
+
The entire browser framework code is in the hub `<lb-hub>`, and its
|
|
219
|
+
only real job is to handle page navigation and the data channel.
|
|
220
|
+
|
|
221
|
+
It also contains addressable methods to update DOM elements with
|
|
222
|
+
data objects, so that custom elements do not have to reproduce that
|
|
223
|
+
code.
|
|
224
|
+
|
|
225
|
+
### Application Server Code
|
|
226
|
+
|
|
227
|
+
For server page code, this is where we truly got accidental complexity
|
|
228
|
+
to zero. A page is just its HTML, its queries, and its request handlers.
|
|
229
|
+
|
|
230
|
+
For the Express server, I was rather surprised myself to find that the
|
|
231
|
+
simplest thing was to show example code for an Express server. Nowadays
|
|
232
|
+
most frameworks provide their own Express server that is so completely
|
|
233
|
+
invisible that a newcomer to the industry can be forgiven for not even
|
|
234
|
+
knowing that Express exists. I wondered if pushing this concern to the
|
|
235
|
+
dev team violated the Principle of Essential Complexity, but I really
|
|
236
|
+
don't think so.
|
|
237
|
+
|
|
238
|
+
The reasoning for not providing a shrink-wrapped
|
|
239
|
+
server is simple: the boilerplate code is one-time effort, and the dev
|
|
240
|
+
team will need to add in their own database handler, security and
|
|
241
|
+
other concerns. A one-time effort may be seen as accidental
|
|
242
|
+
complexity to a skeptic, but its one-time set up means it is not a
|
|
243
|
+
continual tax being paid every time you write a new page. It is that
|
|
244
|
+
sense of paying an ongoing tax that I'm really trying to avoid.
|
|
245
|
+
|
|
246
|
+
### The Database
|
|
247
|
+
|
|
248
|
+
Database concerns are mostly out of scope for the application layer,
|
|
249
|
+
but we must make mention of them because Loadbare is supposed to
|
|
250
|
+
be optimized for database apps.
|
|
251
|
+
|
|
252
|
+
Loadbare satisfies the minimal principle of not being rude by not
|
|
253
|
+
requiring a specific database, neither prohibiting nor requiring
|
|
254
|
+
the ORM of your choice if you use one.
|
|
255
|
+
|
|
256
|
+
However, I should mention that I personally always use
|
|
257
|
+
the `@loadbare/db` package to build
|
|
258
|
+
my databases, which enriches them with calculated values that are
|
|
259
|
+
declarative in the Loadbare db schema. For this reason, I need no
|
|
260
|
+
ORM because the access patterns are very simple: most database reads
|
|
261
|
+
come from one table or a fairly simple view, and user actions that
|
|
262
|
+
update a single table automatically cascade calculations
|
|
263
|
+
to related tables. For this reason, I do not need an ORM and
|
|
264
|
+
my `@loadbare/app` applications connect directly to the database
|
|
265
|
+
with no "business logic" code at all.
|
|
266
|
+
|
|
267
|
+
I must also state that a team not using `@loadbare/db` will probably
|
|
268
|
+
use an ORM or craft a business logic layer, and `@loadbare/app` does
|
|
269
|
+
nothing special for this. It simply provides the empty slot where
|
|
270
|
+
you plug it in.
|
|
271
|
+
|
|
272
|
+
### The Builder
|
|
273
|
+
|
|
274
|
+
The builder kind of fell out for free from the architecture and
|
|
275
|
+
mechanism choices.
|
|
276
|
+
|
|
277
|
+
In addition to what was noted above, it is a very straightforward
|
|
278
|
+
piece of code: it walks the `src/` tree, requiring only a 'chrome.html',
|
|
279
|
+
and picking up all '*.page.html' files. It builds one Javascript file,
|
|
280
|
+
one CSS file, and one HTML file. Having the files be sparse, containing
|
|
281
|
+
only what was used, is trivial.
|
|
282
|
+
|
|
283
|
+
It also builds the application's API from the page's processing
|
|
284
|
+
code, and 4 lines in the Express server end up handling the entire
|
|
285
|
+
API.
|
|
286
|
+
|
|
287
|
+
In short, the builder satisfied the Principle of Essential Complexity, that
|
|
288
|
+
if the code is organized around just application concerns, the builder
|
|
289
|
+
algorithm is self-evident, straightforward, and easy to decorate with
|
|
290
|
+
nice-to-haves.
|
|
291
|
+
|
|
292
|
+
## Assessing the Result
|
|
293
|
+
|
|
294
|
+
I am a biased examiner, but I can say that `@loadbare/app` solves mutliple
|
|
295
|
+
problems for me and achieves compliance with the principles stated
|
|
296
|
+
at the top of this essay.
|
|
297
|
+
|
|
298
|
+
The Principle of Developer Experience says my goal is to make something
|
|
299
|
+
that users actually use and appreciate. Loadbare cannot do that for me,
|
|
300
|
+
but it does free me from so many accidental concerns that my time is
|
|
301
|
+
free to pursue that quality application. It reduces the cost of iteration
|
|
302
|
+
and increases my ability to focus on the shape of the solution. That is
|
|
303
|
+
as much as I can ask for from a productivity tool.
|
|
304
|
+
|
|
305
|
+
In terms of the Principle of User Experience, that we must complete
|
|
306
|
+
round trips from click to paint in a median time of 300ms, Loadbare gives
|
|
307
|
+
me a better chance of doing this than any other tool I have seen.
|
|
308
|
+
The Loadbare app is so light and does so little that if we allow a median
|
|
309
|
+
time of 200ms for wire time, Loadbare likely needs 10ms or less between
|
|
310
|
+
the browser and the server, leaving 90ms for the database and business
|
|
311
|
+
logic. Making the budget in production can be reduced to careful modeling,
|
|
312
|
+
tuning, and hardware spend. (Note: using `@loadbare/db` makes this almost
|
|
313
|
+
a guaranteed win, provided we don't skimp on hardware and know how to tune
|
|
314
|
+
Postgres for production).
|
|
315
|
+
|
|
316
|
+
Finally, in terms of the Primacy of Essential Complexity, I may be a biased
|
|
317
|
+
examiner. Because I wrote it, I see zero accidental complexity, inasmuch
|
|
318
|
+
as everything seems to be there to serve the major principles. But I cannot
|
|
319
|
+
trust my judgment there for obvious reasons. Instead I will propose that
|
|
320
|
+
there are some reasonably obvious wins for Essential Complexity that even
|
|
321
|
+
a skeptic might agree with.
|
|
322
|
+
|
|
323
|
+
One big win is the ability to just write plain HTML. Mixing HTMl and
|
|
324
|
+
Javascript seems cool if you need a fully mutatable DOM, but when you realize
|
|
325
|
+
you don't need that, it's back to plain old reliable HTML.
|
|
326
|
+
|
|
327
|
+
Another big win is the server code for pages. The clean organization
|
|
328
|
+
of hooks, actions, CRUD and queries without boilerplate is pure
|
|
329
|
+
Essential Complexity. The automatic building of the API and 4 lines to
|
|
330
|
+
handle it in Express is lower than I have seen in any other tool.
|
|
331
|
+
|
|
332
|
+
Loadbare satisfies many other principles that do not merit a full explanation,
|
|
333
|
+
such as using technology instead of fighting it, organizing the use of
|
|
334
|
+
browser mechanisms instead of reimplementing them, and encouraging code
|
|
335
|
+
that is likely to have a very long shelf life. Loadbare does not
|
|
336
|
+
interfere with normal CSS usage, so you can bring any solution you like.
|
|
337
|
+
|
|
338
|
+
In closing I would add one more claim. Loadbare is easy to learn because
|
|
339
|
+
it has a small cognitive surface area. The main activity, coding pages,
|
|
340
|
+
is highly repetive and driven by a handful of patterns. It can be learned
|
|
341
|
+
by a person and taught to an LLM with just a few skills.
|
|
342
|
+
|
|
343
|
+
I wrote Loadbare originally in 2003, as "Andromeda", before Node existed
|
|
344
|
+
and even 4 years before we had JQuery. Andromeda was not nearly as
|
|
345
|
+
optimized as Loadbare is now, but the principles were the same then as
|
|
346
|
+
they are now. It has always worked for me, and I hope that it will work
|
|
347
|
+
for you.
|