kopular 1.0.1 → 1.1.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/GUIDE.md +238 -0
- package/LLM.md +51 -25
- package/README.md +17 -40
- package/bin/kp.mjs +44 -149
- package/package.json +5 -2
- package/src/dom.js.map +1 -1
- package/src/dom.ks +3 -0
- package/src/forms.js +4 -4
- package/src/kopular.ks +179 -0
- package/src/router.js +3 -3
- package/src/timers.js +5 -0
- package/src/vdom.js +30 -13
- package/src/vdom.js.map +1 -1
- package/src/vdom.ks +21 -0
- package/src/velement.js +2 -0
- package/src/velement.js.map +1 -1
- package/src/velement.ks +6 -0
package/GUIDE.md
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# Kopular quick guide
|
|
2
|
+
|
|
3
|
+
Everything you need for typical app work, in one page. For anything not covered here —
|
|
4
|
+
generics, nullable types, advanced routing, the full diagnostic list — see this package's
|
|
5
|
+
own `LLM.md` and `node_modules/kopscript/LLM.md`.
|
|
6
|
+
|
|
7
|
+
## Setup
|
|
8
|
+
|
|
9
|
+
Every file starts with `using "kopular";` for the DOM and every Kopular type, plus
|
|
10
|
+
`using "./other_file";` per project file it needs (`using` isn't transitive — list every
|
|
11
|
+
file you reference, not just direct dependencies' dependencies).
|
|
12
|
+
|
|
13
|
+
```ks
|
|
14
|
+
using "kopular";
|
|
15
|
+
using "./counter";
|
|
16
|
+
|
|
17
|
+
Counter app = new Counter();
|
|
18
|
+
app.Mount(document.body);
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Run `npm run build && npm run serve`, or `npm start` (a scaffolded project already has
|
|
22
|
+
both scripts). `ks check src/app.ks` type-checks without building, if you just want errors.
|
|
23
|
+
|
|
24
|
+
## The language you need
|
|
25
|
+
|
|
26
|
+
- `Type name = value;` for every local — no `let`/`var`, no inference on declarations.
|
|
27
|
+
- No implicit `this` — every member reference is `this.Field`/`this.Method()`, always,
|
|
28
|
+
including inside a lambda.
|
|
29
|
+
- `if`/`else`/`while`/`for`/`foreach (Type x in xs)` — all statements, no ternary. Use
|
|
30
|
+
`match` for a conditional value: `match x { 1 => "one", _ => "other" }` (the `_` arm is
|
|
31
|
+
required unless every case is covered, e.g. an enum).
|
|
32
|
+
- Lambdas need explicit parameter types: `(number x) => x * 2`.
|
|
33
|
+
- Nullable: `string? name` — a nullable field forces `if (x != null) { ... }` before use;
|
|
34
|
+
narrowing is scoped to that `if` block, not reachability-based (an early
|
|
35
|
+
`if (x == null) { return; }` does NOT narrow `x` afterward — wrap the rest in
|
|
36
|
+
`if (x != null) { ... }` instead).
|
|
37
|
+
- Strings: `s.Length`, `.Contains()/.StartsWith()/.EndsWith()`, `.Trim()/.ToUpper()/.ToLower()`,
|
|
38
|
+
`.Split(sep)`, `.Replace(from, to)`, `.CompareTo(other)` (sort comparator), `+` concatenates.
|
|
39
|
+
Both `"double"` and `'single'` quotes work — use whichever the other isn't, e.g. inside
|
|
40
|
+
a template attribute (`*if="Name != ''"`).
|
|
41
|
+
- Arrays: `xs.Length`, `.Map()/.Filter()/.ForEach()/.Find()/.FindIndex()/.Includes()/.IndexOf()`,
|
|
42
|
+
`.Sort(cmp)/.Reverse()/.Push(x)` (all three **non-mutating**, return a new array),
|
|
43
|
+
`.Slice(a, b)/.Concat(ys)/.Join(sep)/.Reduce(fn, initial)`.
|
|
44
|
+
- `x == null` / `x != null` also match `undefined` — a missed `Array.Find`, an absent
|
|
45
|
+
optional field, etc. all read as `null`.
|
|
46
|
+
- No object-literal syntax anywhere (`{ key: value }` doesn't exist as a value). A JS API
|
|
47
|
+
that needs one (rare — `fetch`'s options, `addEventListener`'s options object) needs a
|
|
48
|
+
small hand-written `.js` shim; Kopular's own `Http`/`FormField` cover the common cases so
|
|
49
|
+
you'll rarely hit this.
|
|
50
|
+
|
|
51
|
+
## Components
|
|
52
|
+
|
|
53
|
+
```ks
|
|
54
|
+
class Counter : Component {
|
|
55
|
+
public state<number> Count;
|
|
56
|
+
constructor() : base() { this.Count = state(0); }
|
|
57
|
+
public void Increment() { this.Count.Value = this.Count.Value + 1; }
|
|
58
|
+
template from "./counter.html";
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
```html
|
|
62
|
+
<button (click)="Increment()">Count: {{ Count.Value }}</button>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`state<T>` referenced directly in a template auto-subscribes — no manual `Subscribe`
|
|
66
|
+
needed. State reached *indirectly* (through a method, or `this.SomeService.Count`) does
|
|
67
|
+
need one: `this.SomeService.Count.Subscribe((v) => this.Update());` in the constructor.
|
|
68
|
+
|
|
69
|
+
**Template bindings**: `{{ expr }}` text, `[prop]="expr"` (real fields for
|
|
70
|
+
`id`/`className`/`value`/`disabled`/`checked`; anything else is a plain attribute),
|
|
71
|
+
`(click)`/`(input)`/`(blur)`/`(change)` events, `[(value)]="Field"` two-way binding
|
|
72
|
+
(`Field` can be a bare name, `this.Field`, or a path like `Qty.Value` — not a method call),
|
|
73
|
+
`*if="expr"`, `*for="Type v of expr"` (element type required, no inference), `*mount="expr"`
|
|
74
|
+
(embeds a live child component, composes with `*for`).
|
|
75
|
+
|
|
76
|
+
**Hand-written `Render()`** (needed when logic is too dynamic for a template, or a template
|
|
77
|
+
would obscure more than it clarifies):
|
|
78
|
+
|
|
79
|
+
```ks
|
|
80
|
+
public override VElement Render() {
|
|
81
|
+
VElement button = VElement.Create("button");
|
|
82
|
+
button.TextContent = "Count: " + this.Count.Value;
|
|
83
|
+
button.OnClick = (Event e) => { this.Increment(); };
|
|
84
|
+
return button;
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
`VElement.Create(tag)`, `.TextContent/.ClassName/.Id/.Value/.RawHtml/.Disabled/.Checked`,
|
|
88
|
+
`.OnClick/.OnInput/.OnBlur/.OnChange`, `.AppendChild(child)`, `.SetAttr(name, value)` (the
|
|
89
|
+
escape hatch for anything without a named field). Both styles compile to the same thing and
|
|
90
|
+
mix freely across a project.
|
|
91
|
+
|
|
92
|
+
**Error boundary**: override `RenderError(string message)` to show a fallback instead of an
|
|
93
|
+
uncaught crash if `Render()` throws.
|
|
94
|
+
|
|
95
|
+
## Nested components & content projection
|
|
96
|
+
|
|
97
|
+
```ks
|
|
98
|
+
this.Items.ForEach((Item item) => {
|
|
99
|
+
VElement slot = VElement.Mount(item); // item : Component, e.g. its own `public string Id;`
|
|
100
|
+
slot.Id = item.Id; // stable key for reordering
|
|
101
|
+
list.AppendChild(slot);
|
|
102
|
+
});
|
|
103
|
+
```
|
|
104
|
+
A template does the same via `<li *for="Item i of Items" *mount="i"></li>`.
|
|
105
|
+
|
|
106
|
+
Content projection (React's `children`): pass a `() => VElement` into a constructor, call
|
|
107
|
+
it from `Render()` — no separate mechanism needed.
|
|
108
|
+
|
|
109
|
+
## Services — no DI container
|
|
110
|
+
|
|
111
|
+
A service is a plain class; "injecting" it is a constructor argument. Wire everything once
|
|
112
|
+
in a composition root:
|
|
113
|
+
|
|
114
|
+
```ks
|
|
115
|
+
class AppContainer {
|
|
116
|
+
public Router Nav;
|
|
117
|
+
constructor() {
|
|
118
|
+
CounterService counter = new CounterService();
|
|
119
|
+
this.Nav = new Router(new NotFoundPage());
|
|
120
|
+
this.Nav.AddRoute("/", new HomePage(this.Nav, counter));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Router
|
|
126
|
+
|
|
127
|
+
```ks
|
|
128
|
+
Router nav = new Router(new NotFoundPage()); // fallback page, required
|
|
129
|
+
nav.AddRoute("/", new HomePage(nav));
|
|
130
|
+
nav.AddRoute("/dogs/:id", new DogDetailPage(nav)); // nav.Param inside that page
|
|
131
|
+
nav.SetGuard("/login", (string path) => {
|
|
132
|
+
if (path == "/admin") { return authService.LoggedIn; }
|
|
133
|
+
return true;
|
|
134
|
+
});
|
|
135
|
+
nav.Navigate("/dogs/1");
|
|
136
|
+
nav.Mount(document.body);
|
|
137
|
+
```
|
|
138
|
+
- Routes hold already-built `Component`s, not factories — built once, state survives
|
|
139
|
+
navigating away and back.
|
|
140
|
+
- `nav.Param` — the first `:name` segment, a plain `string` (not `state<T>`; the outlet
|
|
141
|
+
re-renders on every navigation already).
|
|
142
|
+
- `SetGuard(redirectPath, guard)`: one guard for the whole router; `guard` returns `false`
|
|
143
|
+
to redirect. Defaults to always-allow.
|
|
144
|
+
- `AddLazyRoute(path, loader)` for real code-splitting — see "Lazy routes" below.
|
|
145
|
+
- Every deploy target needs its own SPA fallback (serve `index.html` for any route with no
|
|
146
|
+
matching file) — a plain HTTP limitation, not a Kopular one.
|
|
147
|
+
|
|
148
|
+
## Forms
|
|
149
|
+
|
|
150
|
+
```ks
|
|
151
|
+
FormField<string> email = new FormField<string>("", (string v) => {
|
|
152
|
+
string? required = Validators.Required(v);
|
|
153
|
+
if (required != null) { return required; }
|
|
154
|
+
return Validators.Email(v);
|
|
155
|
+
});
|
|
156
|
+
email.Value.Value = "not-an-email";
|
|
157
|
+
print(email.Error.Value); // "Must be a valid email"
|
|
158
|
+
```
|
|
159
|
+
`Validators`: `Required/MinLength/MaxLength/Email/Min/Max`, each returns an error message
|
|
160
|
+
or `null`. No array-of-validators param — chain checks in one lambda, as above.
|
|
161
|
+
|
|
162
|
+
## HTTP
|
|
163
|
+
|
|
164
|
+
```ks
|
|
165
|
+
Response r = await Http.Get("/api/dogs");
|
|
166
|
+
if (r.ok) { string body = await r.text(); }
|
|
167
|
+
await Http.Post("/api/dogs", "{\"name\":\"Rex\"}");
|
|
168
|
+
```
|
|
169
|
+
No typed JSON deserialization built in — describe the shape as `extern class` and parse
|
|
170
|
+
with `extern MyShape Parse(string json) as "JSON.parse";`.
|
|
171
|
+
|
|
172
|
+
## Async: `Delay`, `Computed`, `Resource<T>`
|
|
173
|
+
|
|
174
|
+
```ks
|
|
175
|
+
await Delay(500); // real setTimeout-backed delay
|
|
176
|
+
|
|
177
|
+
Computed2<number, number, number> total = new Computed2<number, number, number>(
|
|
178
|
+
qty, price, (number q, number p) => q * p
|
|
179
|
+
);
|
|
180
|
+
total.Value.Subscribe((number v) => this.Update()); // total.Value is itself state<number>
|
|
181
|
+
|
|
182
|
+
Resource<Response> r = new Resource<Response>(Http.Get(url)); // task already in flight
|
|
183
|
+
match r.Status.Value {
|
|
184
|
+
AsyncStatus.Loading => BuildSpinner(),
|
|
185
|
+
AsyncStatus.Success => BuildContent(r.Data.Value),
|
|
186
|
+
AsyncStatus.Failure => BuildError(r.Error.Value)
|
|
187
|
+
};
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Scoped styles
|
|
191
|
+
|
|
192
|
+
```ks
|
|
193
|
+
class Widget : Component {
|
|
194
|
+
template from "./widget.html";
|
|
195
|
+
styles from "./widget.css";
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
Every selector in `widget.css` is rewritten to only match this class's own elements —
|
|
199
|
+
never a sibling's or child's. Needs a constructor to exist on the class.
|
|
200
|
+
|
|
201
|
+
## Lazy routes (real code-splitting)
|
|
202
|
+
|
|
203
|
+
```js
|
|
204
|
+
// admin_page_loader.js — hand-written, not compiled from .ks
|
|
205
|
+
export async function LoadAdminPage() {
|
|
206
|
+
const { AdminPage } = await import("./admin_page.js");
|
|
207
|
+
return new AdminPage();
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
```ks
|
|
211
|
+
extern task<Component> LoadAdminPage() from "./admin_page_loader";
|
|
212
|
+
nav.AddLazyRoute("/admin", LoadAdminPage);
|
|
213
|
+
```
|
|
214
|
+
The lazy page (`admin_page.ks`) must be built as its own entry too — it's deliberately not
|
|
215
|
+
`using`'d from your app's entry (that's what keeps it out of the eager bundle), so add a
|
|
216
|
+
second build line: `ks build src/app.ks && ks build src/admin_page.ks`. Testing it via
|
|
217
|
+
`kopular/testing`'s `runKopularApp` needs no extra setup — it compiles any file in your
|
|
218
|
+
`srcDir` the entry doesn't reach.
|
|
219
|
+
|
|
220
|
+
## Testing
|
|
221
|
+
|
|
222
|
+
```js
|
|
223
|
+
import { runKopularApp } from "kopular/testing";
|
|
224
|
+
const { window, cleanup } = await runKopularApp(srcDir, "app.ks", { includeKopularPackage: true });
|
|
225
|
+
// assert against window.document, then:
|
|
226
|
+
cleanup();
|
|
227
|
+
```
|
|
228
|
+
Real jsdom, real compile, real DOM assertions — not a mock.
|
|
229
|
+
|
|
230
|
+
## Common mistakes
|
|
231
|
+
|
|
232
|
+
- Forgetting `this.` on a member reference inside a lambda — it's a real undefined-identifier
|
|
233
|
+
error, not automatic.
|
|
234
|
+
- `if (x == null) { return; } use(x);` does NOT narrow `x` — see "Nullable" above.
|
|
235
|
+
- A two-way binding target must be a field path (`Field`, `this.Field`, `Field.Value`), never
|
|
236
|
+
a method call.
|
|
237
|
+
- `*mount` accepts only `id`/`[id]` on its element — any other attr/binding is a compile
|
|
238
|
+
error, since the mounted child's own `Render()` owns all of its content.
|
package/LLM.md
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
# Kopular — LLM reference
|
|
2
2
|
|
|
3
3
|
Complete reference for generating correct Kopular code. This is a spec, not a tutorial —
|
|
4
|
-
see `README.md` for narrative/rationale
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
see `README.md` for narrative/rationale, or this package's own **`GUIDE.md`** for a short,
|
|
5
|
+
task-oriented page covering everything a typical app needs (start there for most tasks;
|
|
6
|
+
come back here for anything it doesn't cover). Kopular is 10 files total; this covers all
|
|
7
|
+
of them. For the host language, see KopScript's own `LLM.md` in the `KopScript` repo (or
|
|
8
|
+
its published `LLM.md` on the `kopscript` npm package, `kopscript@1.1.0`+) — that
|
|
9
|
+
reference is a prerequisite, not repeated here.
|
|
8
10
|
|
|
9
11
|
Published as npm `kopular`. Entry points: `kopular` / `kopular/component` (Component),
|
|
10
12
|
`kopular/velement` (VElement — what `Render()` returns; see "Component" below),
|
|
@@ -13,30 +15,50 @@ Published as npm `kopular`. Entry points: `kopular` / `kopular/component` (Compo
|
|
|
13
15
|
(Computed1, Computed2), `kopular/resource` (Resource, AsyncStatus), `kopular/vdom`
|
|
14
16
|
(`ScopedStyles` — the runtime half of `styles from`; everything else in this file, the
|
|
15
17
|
diff/patch engine behind `Update()`, is internal, nothing else here needs importing
|
|
16
|
-
directly), `kopular/testing` (runKopularApp,
|
|
17
|
-
bin, `kp` — `npx kp new
|
|
18
|
-
|
|
19
|
-
scripts needed to run in a browser)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
there is no `kopular/template` entry point to import.
|
|
18
|
+
directly), `kopular/timers` (`Delay`), `kopular/testing` (runKopularApp,
|
|
19
|
+
runKopularFixture — see below). Also ships a bin, `kp` — `npx kp new <dir>` scaffolds a
|
|
20
|
+
new project (an `index.html` with every module already wired, a `Counter` component, and
|
|
21
|
+
the vendor/serve scripts needed to run in a browser). Templates (`template from
|
|
22
|
+
"./x.html";`, see below) are a KopScript language feature, not a Kopular export — there is
|
|
23
|
+
no `kopular/template` entry point to import.
|
|
23
24
|
|
|
24
25
|
## Consuming Kopular from your own KopScript project
|
|
25
26
|
|
|
26
|
-
`using`
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
27
|
+
`using "kopular";` (`kopscript@1.1.0`+) resolves this package's own declarations —
|
|
28
|
+
everything in the "Entry points" list above — through `node_modules`, the same way `npm
|
|
29
|
+
install` already makes them available to plain JS. One line, every Kopular type in scope,
|
|
30
|
+
each consuming file's own compiled output importing only the bindings it actually
|
|
31
|
+
references (never the whole surface). This is the normal way to consume Kopular now; `kp
|
|
32
|
+
new` generates it by default.
|
|
33
|
+
|
|
34
|
+
```ks
|
|
35
|
+
using "kopular";
|
|
36
|
+
using "./counter";
|
|
37
|
+
|
|
38
|
+
Counter app = new Counter();
|
|
39
|
+
app.Mount(document.body);
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`document`/`Element`/`Event`/etc. come from the same `using "kopular";` — no separate ambient
|
|
43
|
+
block needed. The rest of this file describes every one of those bindings' real shapes, for
|
|
44
|
+
when you need something `GUIDE.md` doesn't cover, or need to know exactly what a type looks
|
|
45
|
+
like.
|
|
46
|
+
|
|
47
|
+
### The hand-copied `extern` block (older projects, or trimming what you import)
|
|
48
|
+
|
|
49
|
+
Before `kopscript@1.1.0`, every consuming project hand-declared its own copy of Kopular's
|
|
50
|
+
`extern` bindings (`using` couldn't reach into a package). That still works — a real,
|
|
51
|
+
hand-written `extern` block is exactly as valid as one resolved via `using "kopular";",
|
|
52
|
+
just more to maintain — and is occasionally still the right call if you want a visibly
|
|
53
|
+
trimmed subset. **Don't reach for `Element`/`Document`/`Event` via `extern ... from
|
|
54
|
+
"kopular/dom";`, even though that actually works** (`kopular/dom` genuinely re-exports real
|
|
55
|
+
`globalThis` bindings, same as any other Kopular export) — Kopular's own internal `dom.ks`
|
|
56
|
+
only declares what Kopular's own framework code itself touches, a smaller surface than a
|
|
57
|
+
real app typically needs. `using "kopular";`'s own declarations (`src/kopular.ks` in this
|
|
58
|
+
package) don't have that problem — they're written for consumers, not just for Kopular's
|
|
59
|
+
own internals — so prefer it over a hand-copied ambient block for new code. If you do
|
|
60
|
+
hand-copy one anyway, copy the block below rather than hand-rolling a smaller one from
|
|
61
|
+
scratch and adding members as compile errors demand them — a real, complete first-attempt
|
|
40
62
|
implementation of a Kopular app hit the exact same missing property (`Element.value`)
|
|
41
63
|
twice from two independently-trimmed subsets, because the compile error only ever names
|
|
42
64
|
the one member actually touched, never warns that a *sibling* feature (a template's
|
|
@@ -95,6 +117,10 @@ extern class VElement {
|
|
|
95
117
|
string Id { get; set; }
|
|
96
118
|
string Value { get; set; }
|
|
97
119
|
string RawHtml { get; set; }
|
|
120
|
+
// Real bool properties, not attribute strings — a boolean attribute is on
|
|
121
|
+
// whenever it's present at all, so only a real property can turn it back off.
|
|
122
|
+
bool Disabled { get; set; }
|
|
123
|
+
bool Checked { get; set; }
|
|
98
124
|
(Event) => void OnClick { get; set; }
|
|
99
125
|
(Event) => void OnInput { get; set; }
|
|
100
126
|
(Event) => void OnBlur { get; set; }
|
package/README.md
CHANGED
|
@@ -836,11 +836,11 @@ error, not a runtime blank screen.
|
|
|
836
836
|
|
|
837
837
|
## Starting a new project: `kp new`
|
|
838
838
|
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
839
|
+
`npx kp new my-app` scaffolds a real, working project: a `Counter` component
|
|
840
|
+
(`src/counter.ks`), an `index.html` with every Kopular module already mapped, and the
|
|
841
|
+
vendor/serve scripts needed to run in a browser (a browser can't resolve a bare specifier
|
|
842
|
+
like `"kopular/component"` the way Node's own module resolution does, so `vendor-kopular.mjs`
|
|
843
|
+
copies Kopular's runtime into `vendor/` and `index.html`'s import map points there).
|
|
844
844
|
|
|
845
845
|
```bash
|
|
846
846
|
npx kp new my-app
|
|
@@ -849,37 +849,19 @@ npm install
|
|
|
849
849
|
npm start # builds, vendors kopular's browser files, and serves at :8080
|
|
850
850
|
```
|
|
851
851
|
|
|
852
|
-
|
|
853
|
-
above), the ambient DOM/Kopular `extern` bindings it needs (`src/kopular_bindings.ks`),
|
|
854
|
-
and a `README.md` that points an AI agent at this package's own `LLM.md` before it starts
|
|
852
|
+
Its `README.md` points an AI agent at this package's own `GUIDE.md` before it starts
|
|
855
853
|
generating code. `kp` ships from this package (not from `kopscript`'s own `ks` CLI) since
|
|
856
854
|
scaffolding a *Kopular* app is a framework concern, not a language one — `ks` stays a
|
|
857
855
|
pure-language tool with no framework knowledge baked in.
|
|
858
856
|
|
|
859
857
|
## Using Kopular from another KopScript project
|
|
860
858
|
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
(`kp new` above generates exactly this, if you'd rather not hand-write it):
|
|
859
|
+
`using "kopular";` (`kopscript@1.1.0`+) resolves this package's own KopScript declarations
|
|
860
|
+
through `node_modules` — one line, every Kopular type in scope, with each file's own
|
|
861
|
+
compiled output importing only what it actually references:
|
|
865
862
|
|
|
866
863
|
```ks
|
|
867
|
-
|
|
868
|
-
static VElement Create(string tag);
|
|
869
|
-
string TextContent { get; set; }
|
|
870
|
-
} from "kopular/velement";
|
|
871
|
-
|
|
872
|
-
extern class Component {
|
|
873
|
-
constructor();
|
|
874
|
-
virtual VElement Render();
|
|
875
|
-
void Mount(Element parent);
|
|
876
|
-
} from "kopular/component";
|
|
877
|
-
|
|
878
|
-
extern class Router {
|
|
879
|
-
constructor(Component notFoundPage);
|
|
880
|
-
void AddRoute(string path, Component page);
|
|
881
|
-
void Navigate(string path);
|
|
882
|
-
} from "kopular/router";
|
|
864
|
+
using "kopular";
|
|
883
865
|
|
|
884
866
|
class MyWidget : Component {
|
|
885
867
|
public override VElement Render() {
|
|
@@ -890,19 +872,14 @@ class MyWidget : Component {
|
|
|
890
872
|
}
|
|
891
873
|
```
|
|
892
874
|
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
for a full working example (components, a service, and routing, all consuming Kopular
|
|
896
|
-
this way).
|
|
875
|
+
See [KopularDemo](https://dev.azure.com/koppinator/Koppindependence/_git/KopularDemo) for
|
|
876
|
+
a full working example (components, a service, and routing).
|
|
897
877
|
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
these, `Element`/`Document`/`Event` included. `npx kp new` (above) generates this
|
|
904
|
-
boilerplate for a fresh project either way — reach for `LLM.md`'s block when adding to
|
|
905
|
-
an existing one instead.
|
|
878
|
+
Before `kopscript@1.1.0`, every project hand-declared its own copy of Kopular's `extern`
|
|
879
|
+
bindings instead — `using` couldn't reach into a package yet. That still works (`kp new`
|
|
880
|
+
above can generate it), and is still the right call if you want a visibly trimmed subset —
|
|
881
|
+
see `LLM.md`'s "Consuming Kopular from your own KopScript project" for the complete,
|
|
882
|
+
copy-ready block, `Element`/`Document`/`Event` included.
|
|
906
883
|
|
|
907
884
|
`extern class` can carry its own `<T>` (kopscript >= 0.5.0), so a generic export like
|
|
908
885
|
`FormField<T>` describes the same way a real generic class does — see `LLM.md`'s
|