@backtickjs/core 0.1.7 → 0.1.9
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 +14 -2
- package/dist/ClientScript.d.ts +1 -0
- package/dist/ClientScript.js.map +1 -1
- package/dist/Spliceable.d.ts +3 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/once.d.ts +11 -0
- package/dist/once.js +31 -0
- package/dist/once.js.map +1 -0
- package/package.json +1 -1
- package/src/ClientScript.ts +7 -1
- package/src/Spliceable.ts +6 -2
- package/src/index.ts +3 -1
- package/src/once.ts +40 -0
package/README.md
CHANGED
|
@@ -38,6 +38,17 @@ export async function Home() {
|
|
|
38
38
|
|
|
39
39
|
`$node` and `$Counter` are splices: `node` is a server variable, written into the bundle as data; `Counter` is a script. `$useState` is a splice too, of an export the client provides. What can be spliced is `Spliceable`: data, and scripts. [Thinking in Backtick](https://backtickjs.com/docs/thinking-in-backtick) has what can cross, what can't, and how server and client components compose.
|
|
40
40
|
|
|
41
|
+
A script every component shares, like a context or a store, is made once with `once`: one value for the bundle, where an expression script would run again at each read.
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
import { cs, once } from "@backtickjs/core";
|
|
45
|
+
import { createContext, useContext } from "@backtickjs/react";
|
|
46
|
+
|
|
47
|
+
const ThemeContext = once(cs`$createContext("light")`);
|
|
48
|
+
const Label = cs`() => <p>{$useContext($ThemeContext)}</p>`;
|
|
49
|
+
// In a script: <$ThemeContext.Provider value="dark"><$Label /></$ThemeContext.Provider>
|
|
50
|
+
```
|
|
51
|
+
|
|
41
52
|
To use a package the client provides beyond what an adapter covers, describe its export with `createImport`. `from` is the specifier the bundle imports, and `version` the semver range of its package that the export works with:
|
|
42
53
|
|
|
43
54
|
```ts
|
|
@@ -51,12 +62,13 @@ const greet = createImport<() => string>({
|
|
|
51
62
|
// In a script: cs`<p>{$greet()}</p>`
|
|
52
63
|
```
|
|
53
64
|
|
|
54
|
-
The client must provide that package, and the server must list it in the bundler's `
|
|
65
|
+
The client must provide that package, and the server must list it in the bundler's `modules` at a version in that range.
|
|
55
66
|
|
|
56
67
|
## API
|
|
57
68
|
|
|
58
69
|
- `cs`: the tag for client scripts. Compiled at build time; throws if called uncompiled.
|
|
59
70
|
- `Client<T>`: the type of a script that stands for a `T` on the client.
|
|
71
|
+
- `once(script)`: the script as one value for the bundle, made at its top; each call is one value. Refused for a script that reads a variable of the script around it.
|
|
60
72
|
- `Spliceable`: what a server may splice into a script.
|
|
61
73
|
- `Spliced<T>`: what a spliceable value becomes on the client (`Client<U>` becomes `U`; arrays and objects map member by member).
|
|
62
74
|
- `createImport<T>({ name, from, version })`: an export of a module the client provides, as a value a script can splice.
|
|
@@ -68,7 +80,7 @@ The client must provide that package, and the server must list it in the bundler
|
|
|
68
80
|
## Related
|
|
69
81
|
|
|
70
82
|
- [`@backtickjs/bundler`](https://github.com/backtickjs/backtick/tree/main/packages/bundler): runs server components and writes the bundle
|
|
71
|
-
- [`@backtickjs/react`](https://github.com/backtickjs/backtick/tree/main/
|
|
83
|
+
- [`@backtickjs/react`](https://github.com/backtickjs/backtick/tree/main/adapters/react), [`@backtickjs/react-native`](https://github.com/backtickjs/backtick/tree/main/adapters/react-native), [`@backtickjs/solid-js`](https://github.com/backtickjs/backtick/tree/main/adapters/solid-js): framework adapters
|
|
72
84
|
- [`@backtickjs/node-plugin`](https://github.com/backtickjs/backtick/tree/main/packages/node-plugin), [`@backtickjs/bun-plugin`](https://github.com/backtickjs/backtick/tree/main/packages/bun-plugin): compile scripts as your server loads them
|
|
73
85
|
- [`@backtickjs/tsc`](https://github.com/backtickjs/backtick/tree/main/packages/tsc): type-checks scripts and their splices
|
|
74
86
|
- [Thinking in Backtick](https://backtickjs.com/docs/thinking-in-backtick), [Scripts in depth](https://backtickjs.com/docs/scripts-in-depth)
|
package/dist/ClientScript.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ export interface ClientScript {
|
|
|
18
18
|
readonly "@backtickjs": "ClientScript";
|
|
19
19
|
readonly module: ClientModule;
|
|
20
20
|
readonly args: readonly Spliceable[];
|
|
21
|
+
readonly once?: true;
|
|
21
22
|
}
|
|
22
23
|
export declare function isClientScript(value: unknown): value is ClientScript;
|
|
23
24
|
export declare function create(module: ClientModule, args: readonly Spliceable[]): ClientScript;
|
package/dist/ClientScript.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ClientScript.js","sourceRoot":"","sources":["../src/ClientScript.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ClientScript.js","sourceRoot":"","sources":["../src/ClientScript.ts"],"names":[],"mappings":"AA4CA,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACd,aAAa,IAAI,KAAK;QACtB,KAAK,CAAC,aAAa,CAAC,KAAK,cAAc,CACxC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,MAAM,CACpB,MAAoB,EACpB,IAA2B;IAE3B,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;AACzD,CAAC"}
|
package/dist/Spliceable.d.ts
CHANGED
|
@@ -18,6 +18,8 @@ export type Spliceable = Client<unknown> | null | undefined | number | bigint |
|
|
|
18
18
|
export type SplicesAs<T> = [T] extends [Spliceable] ? T : T extends (...args: never) => unknown ? Spliceable : {
|
|
19
19
|
readonly [K in keyof T]: SplicesAs<T[K]>;
|
|
20
20
|
};
|
|
21
|
-
export type Spliced<T> = T extends Client<infer U> ? U : T extends readonly (infer Item extends Spliceable)[] ? Spliced<Item>[] :
|
|
21
|
+
export type Spliced<T> = T extends Client<infer U> ? U : T extends readonly (infer Item extends Spliceable)[] ? number extends T["length"] ? Spliced<Item>[] : {
|
|
22
|
+
-readonly [K in keyof T]: Spliced<T[K]>;
|
|
23
|
+
} : T extends object ? {
|
|
22
24
|
-readonly [K in keyof T]: Spliced<T[K]>;
|
|
23
25
|
} : T;
|
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export { cs } from "./cs.js";
|
|
6
6
|
export type { Client } from "./Client.js";
|
|
7
|
+
export { once } from "./once.js";
|
|
7
8
|
export type { Spliceable, SplicesAs, Spliced } from "./Spliceable.js";
|
|
8
9
|
export { createImport, isClientImport, type ClientImport, } from "./ClientImport.js";
|
|
9
10
|
export { createJsxElement, isJsxElement, type JsxElement, type JsxElementOf, type JsxElementTypeOf, } from "./JsxElement.js";
|
package/dist/index.js
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
* Backtick: the `cs` tag, what a host may hand a client, and what a template
|
|
3
3
|
* compiles to.
|
|
4
4
|
*/
|
|
5
|
-
// The tag, and how a host language spells a script standing in for a value
|
|
5
|
+
// The tag, and how a host language spells a script standing in for a value;
|
|
6
|
+
// a script as one value for the bundle.
|
|
6
7
|
export { cs } from "./cs.js";
|
|
8
|
+
export { once } from "./once.js";
|
|
7
9
|
export { createImport, isClientImport, } from "./ClientImport.js";
|
|
8
10
|
// A drawing as a host builds one: what an adapter's JSX runtime makes, and
|
|
9
11
|
// what the bundler expands.
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,4EAA4E;AAC5E,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,4EAA4E;AAC5E,wCAAwC;AACxC,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAE7B,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAKjC,OAAO,EACL,YAAY,EACZ,cAAc,GAEf,MAAM,mBAAmB,CAAC;AAE3B,2EAA2E;AAC3E,4BAA4B;AAC5B,OAAO,EACL,gBAAgB,EAChB,YAAY,GAIb,MAAM,iBAAiB,CAAC;AAEzB,6EAA6E;AAC7E,qCAAqC;AACrC,OAAO,EACL,MAAM,EAGN,cAAc,GAEf,MAAM,mBAAmB,CAAC"}
|
package/dist/once.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Client } from "./Client.js";
|
|
2
|
+
/**
|
|
3
|
+
* A script as one value for the bundle: its code runs once, at the top of the
|
|
4
|
+
* bundle, and every read is that value, as a module-level `const` is in plain
|
|
5
|
+
* TypeScript. For what every component shares, a context or a store, which an
|
|
6
|
+
* expression would make again at each read. Each call of `once` is one value.
|
|
7
|
+
*
|
|
8
|
+
* Refused for a script that reads a variable of the script around it: at the
|
|
9
|
+
* top of the bundle, nothing encloses it.
|
|
10
|
+
*/
|
|
11
|
+
export declare function once<T>(script: Client<T>): Client<T>;
|
package/dist/once.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { isClientScript, } from "./ClientScript.js";
|
|
2
|
+
/**
|
|
3
|
+
* A script as one value for the bundle: its code runs once, at the top of the
|
|
4
|
+
* bundle, and every read is that value, as a module-level `const` is in plain
|
|
5
|
+
* TypeScript. For what every component shares, a context or a store, which an
|
|
6
|
+
* expression would make again at each read. Each call of `once` is one value.
|
|
7
|
+
*
|
|
8
|
+
* Refused for a script that reads a variable of the script around it: at the
|
|
9
|
+
* top of the bundle, nothing encloses it.
|
|
10
|
+
*/
|
|
11
|
+
export function once(script) {
|
|
12
|
+
if (!isClientScript(script)) {
|
|
13
|
+
throw new Error("`once` takes a script, cs`…`.");
|
|
14
|
+
}
|
|
15
|
+
const capture = script.module.params.find(isCapture);
|
|
16
|
+
if (capture !== undefined) {
|
|
17
|
+
throw new Error(`Can't make this script \`once\`: it reads \`${sourceName(capture.key)}\` ` +
|
|
18
|
+
"from the script around it, and a `once` value is made at the top " +
|
|
19
|
+
"of the bundle, outside every script.");
|
|
20
|
+
}
|
|
21
|
+
const made = { ...script, once: true };
|
|
22
|
+
return made;
|
|
23
|
+
}
|
|
24
|
+
function isCapture(param) {
|
|
25
|
+
return param.kind === "capture";
|
|
26
|
+
}
|
|
27
|
+
// A binding key is `<name>$<fileHash>$<n>`.
|
|
28
|
+
function sourceName(key) {
|
|
29
|
+
return key.replace(/\$[0-9a-z]+\$\d+$/, "");
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=once.js.map
|
package/dist/once.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"once.js","sourceRoot":"","sources":["../src/once.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,cAAc,GAEf,MAAM,mBAAmB,CAAC;AAE3B;;;;;;;;GAQG;AACH,MAAM,UAAU,IAAI,CAAI,MAAiB;IACvC,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;IACnD,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACrD,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CACb,+CAA+C,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK;YACzE,mEAAmE;YACnE,sCAAsC,CACzC,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,GAAiB,EAAE,GAAG,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IACrD,OAAO,IAA4B,CAAC;AACtC,CAAC;AAED,SAAS,SAAS,CAAC,KAAY;IAC7B,OAAO,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC;AAClC,CAAC;AAED,4CAA4C;AAC5C,SAAS,UAAU,CAAC,GAAW;IAC7B,OAAO,GAAG,CAAC,OAAO,CAAC,mBAAmB,EAAE,EAAE,CAAC,CAAC;AAC9C,CAAC"}
|
package/package.json
CHANGED
package/src/ClientScript.ts
CHANGED
|
@@ -28,12 +28,18 @@ export interface ClientModule {
|
|
|
28
28
|
readonly kind: "function" | "expression" | "block";
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
// One run of a `cs`: its module, and the values that run passes it.
|
|
31
|
+
// One run of a `cs`: its module, and the values that run passes it. The rest
|
|
32
|
+
// are flags, how the bundle treats the script: each optional, absent meaning
|
|
33
|
+
// the plain script, so code that makes or spreads a script without knowing a
|
|
34
|
+
// flag reads the same.
|
|
32
35
|
export interface ClientScript {
|
|
33
36
|
readonly "@backtickjs": "ClientScript";
|
|
34
37
|
readonly module: ClientModule;
|
|
35
38
|
// one per splice, in parameter order
|
|
36
39
|
readonly args: readonly Spliceable[];
|
|
40
|
+
// one value for the bundle, made at its top, rather than code run at each
|
|
41
|
+
// read: see `once`
|
|
42
|
+
readonly once?: true;
|
|
37
43
|
}
|
|
38
44
|
|
|
39
45
|
export function isClientScript(value: unknown): value is ClientScript {
|
package/src/Spliceable.ts
CHANGED
|
@@ -32,14 +32,18 @@ export type SplicesAs<T> = [T] extends [Spliceable]
|
|
|
32
32
|
|
|
33
33
|
// What a spliceable becomes on the client:
|
|
34
34
|
// Client<U> -> U
|
|
35
|
-
// T[]
|
|
35
|
+
// T[], [A, B] -> Spliced<T>[], [Spliced<A>, Spliced<B>]
|
|
36
36
|
// { k: T } -> { k: Spliced<T> }
|
|
37
37
|
// primitives -> unchanged
|
|
38
|
+
// A tuple maps homomorphically, which keeps it a tuple; any other array stays
|
|
39
|
+
// `Spliced<Item>[]`, which TypeScript defers, so `Spliceable[]` doesn't recurse.
|
|
38
40
|
export type Spliced<T> =
|
|
39
41
|
T extends Client<infer U>
|
|
40
42
|
? U
|
|
41
43
|
: T extends readonly (infer Item extends Spliceable)[]
|
|
42
|
-
?
|
|
44
|
+
? number extends T["length"]
|
|
45
|
+
? Spliced<Item>[]
|
|
46
|
+
: { -readonly [K in keyof T]: Spliced<T[K]> }
|
|
43
47
|
: T extends object
|
|
44
48
|
? { -readonly [K in keyof T]: Spliced<T[K]> }
|
|
45
49
|
: T;
|
package/src/index.ts
CHANGED
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
* compiles to.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
// The tag, and how a host language spells a script standing in for a value
|
|
6
|
+
// The tag, and how a host language spells a script standing in for a value;
|
|
7
|
+
// a script as one value for the bundle.
|
|
7
8
|
export { cs } from "./cs.js";
|
|
8
9
|
export type { Client } from "./Client.js";
|
|
10
|
+
export { once } from "./once.js";
|
|
9
11
|
|
|
10
12
|
// What a host may hand a client: data, a script, or an export of a module the
|
|
11
13
|
// client provides.
|
package/src/once.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Client } from "./Client.js";
|
|
2
|
+
import {
|
|
3
|
+
type ClientScript,
|
|
4
|
+
isClientScript,
|
|
5
|
+
type Param,
|
|
6
|
+
} from "./ClientScript.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A script as one value for the bundle: its code runs once, at the top of the
|
|
10
|
+
* bundle, and every read is that value, as a module-level `const` is in plain
|
|
11
|
+
* TypeScript. For what every component shares, a context or a store, which an
|
|
12
|
+
* expression would make again at each read. Each call of `once` is one value.
|
|
13
|
+
*
|
|
14
|
+
* Refused for a script that reads a variable of the script around it: at the
|
|
15
|
+
* top of the bundle, nothing encloses it.
|
|
16
|
+
*/
|
|
17
|
+
export function once<T>(script: Client<T>): Client<T> {
|
|
18
|
+
if (!isClientScript(script)) {
|
|
19
|
+
throw new Error("`once` takes a script, cs`…`.");
|
|
20
|
+
}
|
|
21
|
+
const capture = script.module.params.find(isCapture);
|
|
22
|
+
if (capture !== undefined) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
`Can't make this script \`once\`: it reads \`${sourceName(capture.key)}\` ` +
|
|
25
|
+
"from the script around it, and a `once` value is made at the top " +
|
|
26
|
+
"of the bundle, outside every script.",
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
const made: ClientScript = { ...script, once: true };
|
|
30
|
+
return made as unknown as Client<T>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function isCapture(param: Param): param is Extract<Param, { kind: "capture" }> {
|
|
34
|
+
return param.kind === "capture";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// A binding key is `<name>$<fileHash>$<n>`.
|
|
38
|
+
function sourceName(key: string): string {
|
|
39
|
+
return key.replace(/\$[0-9a-z]+\$\d+$/, "");
|
|
40
|
+
}
|