marko 5.22.7 → 5.22.8
Sign up to get free protection for your applications and to get access to all the features.
- package/docs/icons/js.svg +4 -0
- package/docs/icons/marko.svg +1 -0
- package/docs/icons/ts.svg +1 -0
- package/docs/structure.json +1 -0
- package/docs/typescript.md +359 -0
- package/package.json +1 -1
@@ -0,0 +1,4 @@
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 630 630">
|
2
|
+
<rect width="630" height="630" fill="#f7df1e"/>
|
3
|
+
<path d="m423.2 492.19c12.69 20.72 29.2 35.95 58.4 35.95 24.53 0 40.2-12.26 40.2-29.2 0-20.3-16.1-27.49-43.1-39.3l-14.8-6.35c-42.72-18.2-71.1-41-71.1-89.2 0-44.4 33.83-78.2 86.7-78.2 37.64 0 64.7 13.1 84.2 47.4l-46.1 29.6c-10.15-18.2-21.1-25.37-38.1-25.37-17.34 0-28.33 11-28.33 25.37 0 17.76 11 24.95 36.4 35.95l14.8 6.34c50.3 21.57 78.7 43.56 78.7 93 0 53.3-41.87 82.5-98.1 82.5-54.98 0-90.5-26.2-107.88-60.54zm-209.13 5.13c9.3 16.5 17.76 30.45 38.1 30.45 19.45 0 31.72-7.61 31.72-37.2v-201.3h59.2v202.1c0 61.3-35.94 89.2-88.4 89.2-47.4 0-74.85-24.53-88.81-54.075z"/>
|
4
|
+
</svg>
|
@@ -0,0 +1 @@
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="512" viewBox="0 0 2560 1400"><path fill="url(#a)" d="M427 0h361L361 697l427 697H427L0 698z"/><linearGradient id="a" x2="0" y2="1"><stop offset="0" stop-color="hsl(181, 96.3%, 38.8%)"/><stop offset=".25" stop-color="hsl(186, 94.9%, 46.1%)"/><stop offset=".5" stop-color="hsl(191, 93.3%, 60.8%)"/><stop offset=".5" stop-color="hsl(195, 94.3%, 50.8%)"/><stop offset=".75" stop-color="hsl(199, 95.9%, 48.0%)"/><stop offset="1" stop-color="hsl(203, 94.9%, 38.6%)"/></linearGradient><path fill="url(#b)" d="M854 697h361L788 0H427z"/><linearGradient id="b" x2="0" y2="1"><stop offset="0" stop-color="hsl(170, 80.3%, 50.8%)"/><stop offset=".5" stop-color="hsl(161, 79.1%, 47.3%)"/><stop offset="1" stop-color="hsl(157, 78.1%, 38.9%)"/></linearGradient><path fill="url(#c)" d="M1281 0h361l-427 697H854z"/><linearGradient id="c" x2="0" y2="1"><stop offset="0" stop-color="hsl(86, 95.9%, 37.1%)"/><stop offset=".5" stop-color="hsl(86, 91.9%, 45.0%)"/><stop offset="1" stop-color="hsl(90, 82.1%, 51.2%)"/></linearGradient><path fill="url(#d)" d="M1642 0h-361l428 697-428 697h361l428-697z"/><linearGradient id="d" x2="0" y2="1"><stop offset="0" stop-color="hsl(55, 99.9%, 53.1%)"/><stop offset=".25" stop-color="hsl(51, 99.9%, 50.0%)"/><stop offset=".5" stop-color="hsl(47, 99.2%, 49.8%)"/><stop offset=".5" stop-color="hsl(39, 99.9%, 50.0%)"/><stop offset=".75" stop-color="hsl(35, 99.9%, 50.0%)"/><stop offset="1" stop-color="hsl(29, 99.9%, 46.9%)"/></linearGradient><path fill="url(#e)" d="M2132 0h-361l427 697-428 697h361l428-697z"/><linearGradient id="e" x2="0" y2="1"><stop offset="0" stop-color="hsl(352, 99.9%, 62.9%)"/><stop offset=".25" stop-color="hsl(345, 90.3%, 51.8%)"/><stop offset=".5" stop-color="hsl(341, 88.3%, 51.8%)"/><stop offset=".5" stop-color="hsl(336, 80.9%, 45.4%)"/><stop offset=".75" stop-color="hsl(332, 80.3%, 44.8%)"/><stop offset="1.1" stop-color="hsl(328, 78.1%, 35.9%)"/></linearGradient></svg>
|
@@ -0,0 +1 @@
|
|
1
|
+
<svg fill="none" height="512" viewBox="0 0 512 512" width="512" xmlns="http://www.w3.org/2000/svg"><rect fill="#3178c6" height="512" rx="50" width="512"/><rect fill="#3178c6" height="512" rx="50" width="512"/><path clip-rule="evenodd" d="m316.939 407.424v50.061c8.138 4.172 17.763 7.3 28.875 9.386s22.823 3.129 35.135 3.129c11.999 0 23.397-1.147 34.196-3.442 10.799-2.294 20.268-6.075 28.406-11.342 8.138-5.266 14.581-12.15 19.328-20.65s7.121-19.007 7.121-31.522c0-9.074-1.356-17.026-4.069-23.857s-6.625-12.906-11.738-18.225c-5.112-5.319-11.242-10.091-18.389-14.315s-15.207-8.213-24.18-11.967c-6.573-2.712-12.468-5.345-17.685-7.9-5.217-2.556-9.651-5.163-13.303-7.822-3.652-2.66-6.469-5.476-8.451-8.448-1.982-2.973-2.974-6.336-2.974-10.091 0-3.441.887-6.544 2.661-9.308s4.278-5.136 7.512-7.118c3.235-1.981 7.199-3.52 11.894-4.615 4.696-1.095 9.912-1.642 15.651-1.642 4.173 0 8.581.313 13.224.938 4.643.626 9.312 1.591 14.008 2.894 4.695 1.304 9.259 2.947 13.694 4.928 4.434 1.982 8.529 4.276 12.285 6.884v-46.776c-7.616-2.92-15.937-5.084-24.962-6.492s-19.381-2.112-31.066-2.112c-11.895 0-23.163 1.278-33.805 3.833s-20.006 6.544-28.093 11.967c-8.086 5.424-14.476 12.333-19.171 20.729-4.695 8.395-7.043 18.433-7.043 30.114 0 14.914 4.304 27.638 12.912 38.172 8.607 10.533 21.675 19.45 39.204 26.751 6.886 2.816 13.303 5.579 19.25 8.291s11.086 5.528 15.415 8.448c4.33 2.92 7.747 6.101 10.252 9.543 2.504 3.441 3.756 7.352 3.756 11.733 0 3.233-.783 6.231-2.348 8.995s-3.939 5.162-7.121 7.196-7.147 3.624-11.894 4.771c-4.748 1.148-10.303 1.721-16.668 1.721-10.851 0-21.597-1.903-32.24-5.71-10.642-3.806-20.502-9.516-29.579-17.13zm-84.159-123.342h64.22v-41.082h-179v41.082h63.906v182.918h50.874z" fill="#fff" fill-rule="evenodd"/></svg>
|
package/docs/structure.json
CHANGED
@@ -0,0 +1,359 @@
|
|
1
|
+
# TypeScript in Marko
|
2
|
+
|
3
|
+
> **Note:** Types are supported in Marko v5.22.7+ and Marko v4.24.6+
|
4
|
+
|
5
|
+
Marko’s TypeScript support offers in-editor error checking, makes refactoring less scary, verifies that data matches expectations, and even helps with API design.
|
6
|
+
|
7
|
+
Or maybe you just want more autocomplete in VSCode. That works too.
|
8
|
+
|
9
|
+
## Enabling TypeScript in your Marko project
|
10
|
+
|
11
|
+
There are two (non-exclusive) ways to add TypeScript to a Marko project:
|
12
|
+
|
13
|
+
- **For sites and web apps**, you can place [a `tsconfig.json` file](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html) at the project root:
|
14
|
+
<pre>
|
15
|
+
📁 components/
|
16
|
+
📁 node_modules/
|
17
|
+
<img src="./icons/marko.svg" width=16> index.marko
|
18
|
+
📦 package.json
|
19
|
+
<mark><img src="./icons/ts.svg" width=16> tsconfig.json</mark>
|
20
|
+
</pre>
|
21
|
+
- **If you’re [publishing packages of Marko tags](https://markojs.com/docs/custom-tags/#publishing-tags-to-npm)**, add the following to [your `marko.json`](https://markojs.com/docs/marko-json/):
|
22
|
+
```json
|
23
|
+
"script-lang": "ts"
|
24
|
+
```
|
25
|
+
This will automatically expose type-checking and autocomplete for the published tags.
|
26
|
+
|
27
|
+
> **ProTip**: You can also use the `script-lang` method for sites and apps.
|
28
|
+
|
29
|
+
## Typing a tag's `input`
|
30
|
+
|
31
|
+
A `.marko` file will use any exported `Input` type for [that file’s `input` object](https://markojs.com/docs/class-components/#input).
|
32
|
+
|
33
|
+
This can be `export type Input` or `export interface Input`.
|
34
|
+
|
35
|
+
### Example
|
36
|
+
|
37
|
+
_PriceField.marko_
|
38
|
+
|
39
|
+
```marko
|
40
|
+
export interface Input {
|
41
|
+
currency: string;
|
42
|
+
amount: number;
|
43
|
+
}
|
44
|
+
|
45
|
+
<label>
|
46
|
+
Price in ${input.currency}:
|
47
|
+
<input type="number" value=input.amount min=0 step=0.01>
|
48
|
+
</label>
|
49
|
+
```
|
50
|
+
|
51
|
+
You can also import, reuse, and extend `Input` interfaces from other `.marko` or `.ts` files:
|
52
|
+
|
53
|
+
```marko
|
54
|
+
import { Input as PriceInput } from "<PriceField>";
|
55
|
+
import { ExtraTypes } from "lib/utils.ts";
|
56
|
+
export type Input = { ...PriceInput, ...ExtraTypes };
|
57
|
+
```
|
58
|
+
|
59
|
+
```marko
|
60
|
+
import { Input as PriceInput } from "<PriceField>";
|
61
|
+
export interface Input extends PriceInput {
|
62
|
+
discounted: boolean;
|
63
|
+
expiresAt: Date;
|
64
|
+
};
|
65
|
+
```
|
66
|
+
|
67
|
+
### Generic `Input`s
|
68
|
+
|
69
|
+
[Generic Types and Type Parameters](https://www.typescriptlang.org/docs/handbook/2/generics.html) on `Input` are recognized throughout the entire `.marko` template (excluding [static statements](https://markojs.com/docs/syntax/#static-javascript)).
|
70
|
+
|
71
|
+
For example, if you set up a component like this:
|
72
|
+
|
73
|
+
_components/my-select.marko_
|
74
|
+
|
75
|
+
```marko
|
76
|
+
export interface Input<T> {
|
77
|
+
options: T[];
|
78
|
+
onSelect: (newVal: T) => unknown;
|
79
|
+
}
|
80
|
+
|
81
|
+
static function staticFn() {
|
82
|
+
// can NOT use `T` here
|
83
|
+
}
|
84
|
+
|
85
|
+
$ const instanceFn = (val: T) => {
|
86
|
+
// can use `T` here
|
87
|
+
}
|
88
|
+
|
89
|
+
// can use `as T` here
|
90
|
+
<select on-input(evt => input.onSelect(options[evt.target.value] as T))>
|
91
|
+
<for|value, i| of=input.options>
|
92
|
+
<option value=i>${value}</option>
|
93
|
+
</for>
|
94
|
+
</select>
|
95
|
+
```
|
96
|
+
|
97
|
+
…then your editor will figure out the types of inputs to that component:
|
98
|
+
|
99
|
+
```marko
|
100
|
+
<my-select options=[1,2,3] onSelect=val => {}/>
|
101
|
+
// ^^^ number
|
102
|
+
|
103
|
+
<my-select options=["M","K","O"] onSelect=val => {}/>
|
104
|
+
// ^^^ string
|
105
|
+
```
|
106
|
+
|
107
|
+
## Built-in Marko Types
|
108
|
+
|
109
|
+
Marko exposes [type definitions](https://github.com/marko-js/marko/blob/main/packages/marko/index.d.ts) you can reuse in [a TypeScript namespace](https://www.typescriptlang.org/docs/handbook/namespaces.html) called `Marko`:
|
110
|
+
|
111
|
+
- **`Marko.Template<Input, Return>`**
|
112
|
+
- The type of a `.marko` file
|
113
|
+
- `typeof import("./template.marko")`
|
114
|
+
- **`Marko.TemplateInput<Input>`**
|
115
|
+
- The object accepted by the render methods of a template. It includes the template's `Input` as well as `$global` values.
|
116
|
+
- **`Marko.Body<Params, Return>`**
|
117
|
+
- The type of the [body content](https://markojs.com/docs/body-content/) of a tag (`renderBody`)
|
118
|
+
- **`Marko.Component<Input, State>`**
|
119
|
+
- The base class for a [class component](https://markojs.com/docs/class-components/)
|
120
|
+
- **`Marko.Renderable`**
|
121
|
+
- Values accepted by the [`<${dynamic}/>` tag](https://markojs.com/docs/syntax/#dynamic-tagname)
|
122
|
+
- `string | Marko.Template | Marko.Body | { renderBody: Marko.Body}`
|
123
|
+
- **`Marko.Out`**
|
124
|
+
- The render context with methods like `write`, `beginAsync`, etc.
|
125
|
+
- `ReturnType<template.render>`
|
126
|
+
- **`Marko.Global`**
|
127
|
+
- The type of the object on `out.global` that can be passed to a template's render methods as the `$global` property
|
128
|
+
- **`Marko.RenderResult`**
|
129
|
+
- The [result](https://markojs.com/docs/rendering/#renderresult) of rendering a Marko template
|
130
|
+
- `ReturnType<template.renderSync>`
|
131
|
+
- `Awaited<ReturnType<template.render>>`
|
132
|
+
- **`Marko.Emitter`**
|
133
|
+
- `EventEmitter` from `@types/node`
|
134
|
+
- **`Marko.NativeTags`**
|
135
|
+
- `Marko.NativeTags`: An object containing all native tags and their types
|
136
|
+
- **`Marko.NativeTagInput<TagName>`** and **`Marko.NativeTagReturn<TagName>`**
|
137
|
+
- Helpers to extract the input and return types for the specified `keyof Marko.NativeTag`
|
138
|
+
- **`Marko.BodyParameters<Body>`** and **`Marko.BodyReturnType<Body>`**
|
139
|
+
- Helpers to extract the parameters and return types from the specified `Marko.Body`
|
140
|
+
- **`Marko.Repeated<T>`** and **`Marko.Repeatable<T>`**
|
141
|
+
- Used to represent types for attributes tags which can have one or many instances
|
142
|
+
- `Marko.Repeated<T>`: `[T, T, ...T[]]` (array with at least two items)
|
143
|
+
- `Marko.Repeatable<T>`: `T | Marko.Repeated<T>`
|
144
|
+
|
145
|
+
### Typing `renderBody`
|
146
|
+
|
147
|
+
The most commonly used type from the `Marko` namespace is `Marko.Body` which can be used to type `input.renderBody`:
|
148
|
+
|
149
|
+
_child.marko_
|
150
|
+
|
151
|
+
```marko
|
152
|
+
export interface Input {
|
153
|
+
renderBody?: Marko.Body;
|
154
|
+
}
|
155
|
+
```
|
156
|
+
|
157
|
+
Here, the following will be acceptable values:
|
158
|
+
|
159
|
+
_index.marko_
|
160
|
+
|
161
|
+
```marko
|
162
|
+
<child/>
|
163
|
+
<child>Text in render body</child>
|
164
|
+
<child>
|
165
|
+
<div>Any combination of components</div>
|
166
|
+
</child>
|
167
|
+
```
|
168
|
+
|
169
|
+
Passing other values (including components) will cause a type error:
|
170
|
+
|
171
|
+
_index.marko_
|
172
|
+
|
173
|
+
```marko
|
174
|
+
import OtherTag from "<other-tag>";
|
175
|
+
<child renderBody=OtherTag/>
|
176
|
+
```
|
177
|
+
|
178
|
+
### Typing Tag Parameters
|
179
|
+
|
180
|
+
Tag parameters are passed to the `renderBody` by the child tag. For this reason, `Marko.Body` also allows typing of its parameters:
|
181
|
+
|
182
|
+
_for-by-two.marko_
|
183
|
+
|
184
|
+
```marko
|
185
|
+
export interface Input {
|
186
|
+
to: number;
|
187
|
+
renderBody: Marko.Body<[number]>
|
188
|
+
}
|
189
|
+
|
190
|
+
<for|i| from=0 to=input.to by=2>
|
191
|
+
<${input.renderBody}(i)/>
|
192
|
+
</for>
|
193
|
+
```
|
194
|
+
|
195
|
+
_index.marko_
|
196
|
+
|
197
|
+
```marko
|
198
|
+
<for-by-two|i| to=10>
|
199
|
+
<div>${i}</div>
|
200
|
+
</for-by-two>
|
201
|
+
```
|
202
|
+
|
203
|
+
### Extending a native tag type
|
204
|
+
|
205
|
+
The types for native tags are accessed via the global `Marko.NativeTags` type. Here's an example of a component that adds a feature to the `button` element:
|
206
|
+
|
207
|
+
_color-button.marko_
|
208
|
+
|
209
|
+
```marko
|
210
|
+
export interface Input extends Marko.NativeTagInput<"button"> {
|
211
|
+
color: string;
|
212
|
+
renderBody?: Marko.Body;
|
213
|
+
}
|
214
|
+
|
215
|
+
$ const { color, renderBody, ...restOfInput } = input;
|
216
|
+
|
217
|
+
<button style=`color: ${color}` ...restOfInput>
|
218
|
+
<${renderBody}/>
|
219
|
+
</button>
|
220
|
+
```
|
221
|
+
|
222
|
+
## TypeScript Syntax in `.marko`
|
223
|
+
|
224
|
+
Any [JavaScript expression in Marko](https://markojs.com/docs/syntax/#inline-javascript) can also be written as a TypeScript expression.
|
225
|
+
|
226
|
+
### Tag Type Parameters
|
227
|
+
|
228
|
+
```marko
|
229
|
+
<child <T>|value: T|>
|
230
|
+
...
|
231
|
+
</child>
|
232
|
+
```
|
233
|
+
|
234
|
+
### Tag Type Arguments
|
235
|
+
|
236
|
+
_components/child.marko_
|
237
|
+
|
238
|
+
```marko
|
239
|
+
export interface Input<T> {
|
240
|
+
value: T;
|
241
|
+
}
|
242
|
+
```
|
243
|
+
|
244
|
+
_index.marko_
|
245
|
+
|
246
|
+
```marko
|
247
|
+
// number would be inferred in this case, but we can be explicit
|
248
|
+
<child<number> value=1 />
|
249
|
+
```
|
250
|
+
|
251
|
+
### Method Shorthand Type Parameters
|
252
|
+
|
253
|
+
```marko
|
254
|
+
<child process<T>() { /* ... */ } />
|
255
|
+
```
|
256
|
+
|
257
|
+
### Attribute Type Assertions
|
258
|
+
|
259
|
+
The types of attribute values can _usually_ be inferred. When needed, you can assert values to be more specific with [TypeScript’s `as` keyword](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#type-assertions):
|
260
|
+
|
261
|
+
```marko
|
262
|
+
<some-component
|
263
|
+
number=1 as const
|
264
|
+
names=[] as string[]
|
265
|
+
/>
|
266
|
+
```
|
267
|
+
|
268
|
+
# JSDoc Support
|
269
|
+
|
270
|
+
For existing projects that want to incrementally add type safety, adding full TypeScript support is a big leap. This is why Marko also includes full support for [incremental typing via JSDoc](https://www.typescriptlang.org/docs/handbook/intro-to-js-ts.html).
|
271
|
+
|
272
|
+
## Setup
|
273
|
+
|
274
|
+
You can enable type checking in an existing `.marko` file by adding a `// @ts-check` comment at the top:
|
275
|
+
|
276
|
+
```js
|
277
|
+
// @ts-check
|
278
|
+
```
|
279
|
+
|
280
|
+
If you want to enable type checking for all Marko & JavaScript files in a JavaScript project, you can switch to using a [`jsconfig.json`](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html#using-tsconfigjson-or-jsconfigjson). You can skip checking some files by adding a `// @ts-nocheck` comment to files.
|
281
|
+
|
282
|
+
Once that has been enabled, you can start by typing the input with JSDoc. Here's an example component with typed `input`:
|
283
|
+
|
284
|
+
```marko
|
285
|
+
// @ts-check
|
286
|
+
|
287
|
+
/**
|
288
|
+
* @typedef {{
|
289
|
+
* firstName: string,
|
290
|
+
* lastName: string,
|
291
|
+
* }} Input
|
292
|
+
*/
|
293
|
+
|
294
|
+
<div>${firstName} ${lastName}</div>
|
295
|
+
```
|
296
|
+
|
297
|
+
## With a separate `component.js` file
|
298
|
+
|
299
|
+
Many components in existing projects adhere to the following structure:
|
300
|
+
|
301
|
+
<pre>
|
302
|
+
📁 components/
|
303
|
+
📁 color-rotate-button/
|
304
|
+
<img src="./icons/marko.svg" width=16> index.marko
|
305
|
+
<img src="./icons/js.svg" width=16> component.js
|
306
|
+
</pre>
|
307
|
+
|
308
|
+
The `color-rotate-button` takes a list of colors and moves to the next one each time the button is clicked:
|
309
|
+
|
310
|
+
```marko
|
311
|
+
<color-rotate-button colors=["red", "blue", "yellow"]>
|
312
|
+
Next Color
|
313
|
+
</color-rotate-button>
|
314
|
+
```
|
315
|
+
|
316
|
+
Here is an example of how this `color-rotate-button` component could be typed:
|
317
|
+
|
318
|
+
_components/color-rotate-button/component.js_
|
319
|
+
|
320
|
+
```js
|
321
|
+
// @ts-check
|
322
|
+
|
323
|
+
/**
|
324
|
+
* @typedef {{
|
325
|
+
* colors: string[],
|
326
|
+
* renderBody: Marko.Renderable
|
327
|
+
* }} Input
|
328
|
+
* @typedef {{
|
329
|
+
* colorIndex: number
|
330
|
+
* }} State
|
331
|
+
* @extends {Marko.Component<Input, State>}
|
332
|
+
*/
|
333
|
+
export default class extends Marko.Component {
|
334
|
+
onCreate() {
|
335
|
+
this.state = {
|
336
|
+
colorIndex: 0
|
337
|
+
};
|
338
|
+
}
|
339
|
+
|
340
|
+
rotateColor() {
|
341
|
+
this.state.colorIndex =
|
342
|
+
(this.state.colorIndex + 1) % this.input.colors.length;
|
343
|
+
}
|
344
|
+
}
|
345
|
+
```
|
346
|
+
|
347
|
+
_components/color-rotate-button/index.marko_
|
348
|
+
|
349
|
+
```marko
|
350
|
+
// @ts-check
|
351
|
+
|
352
|
+
/* Input will be automatically imported from `component.js`! */
|
353
|
+
|
354
|
+
<button
|
355
|
+
onClick('rotateColor')
|
356
|
+
style=`color: ${input.colors[state.colorIndex]}`>
|
357
|
+
<${input.renderBody}/>
|
358
|
+
</button>
|
359
|
+
```
|