@backtickjs/core 0.1.10 → 0.2.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 +16 -17
- package/dist/Bundle.d.ts +9 -0
- package/dist/Bundle.js +2 -0
- package/dist/Bundle.js.map +1 -0
- package/dist/ClientImport.d.ts +2 -1
- package/dist/ClientImport.js.map +1 -1
- package/dist/ClientModule.d.ts +8 -0
- package/dist/ClientModule.js +2 -0
- package/dist/ClientModule.js.map +1 -0
- package/dist/ClientScript.d.ts +4 -4
- package/dist/ClientScript.js.map +1 -1
- package/dist/JsxElement.d.ts +2 -2
- package/dist/ModuleMap.d.ts +16 -0
- package/dist/ModuleMap.js +2 -0
- package/dist/ModuleMap.js.map +1 -0
- package/dist/Spliceable.d.ts +1 -1
- package/dist/cjs.d.ts +14 -0
- package/dist/cjs.js +22 -0
- package/dist/cjs.js.map +1 -0
- package/dist/es.d.ts +11 -0
- package/dist/es.js +14 -0
- package/dist/es.js.map +1 -0
- package/dist/exec.d.ts +14 -0
- package/dist/exec.js +10 -0
- package/dist/exec.js.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/package.json +9 -1
- package/src/Bundle.ts +7 -0
- package/src/ClientImport.ts +4 -2
- package/src/ClientModule.ts +7 -0
- package/src/ClientScript.ts +6 -5
- package/src/JsxElement.ts +3 -3
- package/src/ModuleMap.ts +16 -0
- package/src/Spliceable.ts +6 -4
- package/src/cjs.ts +35 -0
- package/src/es.ts +17 -0
- package/src/exec.ts +28 -0
- package/src/index.ts +8 -2
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The `cs` tag and the types around it. A client script, written `` cs`…` `` inside a server component, is code that runs on the client; values from the server cross into it as splices written `$name`, type-checked on both sides. Every Backtick app imports `cs` from here, whatever its UI framework.
|
|
4
4
|
|
|
5
|
-
`cs` is compiled at build time, so a project also needs a build plugin (`@backtickjs/node-plugin` or `@backtickjs/bun-plugin`),
|
|
5
|
+
`cs` is compiled at build time, so a project also needs a build plugin (`@backtickjs/node-plugin` or `@backtickjs/bun-plugin`), its framework's JSX package (`@backtickjs/jsx-react` or `@backtickjs/jsx-solid`) and `@backtickjs/tsc` for type checking. Called without the compiler, `cs` throws. `npx create-backtick-app@latest` sets all of this up.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -16,7 +16,7 @@ A server component reads data on the server and returns a client script. A clien
|
|
|
16
16
|
|
|
17
17
|
```tsx
|
|
18
18
|
import { cs } from "@backtickjs/core";
|
|
19
|
-
import { useState } from "
|
|
19
|
+
import { useState } from "react" with { backtick: "client" };
|
|
20
20
|
|
|
21
21
|
// A client component: it runs on the client.
|
|
22
22
|
const Counter = cs`(props: { start: number }) => {
|
|
@@ -36,33 +36,27 @@ export async function Home() {
|
|
|
36
36
|
}
|
|
37
37
|
```
|
|
38
38
|
|
|
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.
|
|
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
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
42
|
|
|
43
43
|
```tsx
|
|
44
44
|
import { cs, once } from "@backtickjs/core";
|
|
45
|
-
import { createContext, useContext } from "
|
|
45
|
+
import { createContext, useContext } from "react" with { backtick: "client" };
|
|
46
46
|
|
|
47
47
|
const ThemeContext = once(cs`$createContext("light")`);
|
|
48
48
|
const Label = cs`() => <p>{$useContext($ThemeContext)}</p>`;
|
|
49
49
|
// In a script: <$ThemeContext.Provider value="dark"><$Label /></$ThemeContext.Provider>
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
-
To use a package the client provides
|
|
52
|
+
To use a package the client provides, import it with `with { backtick: "client" }`. The server never loads it: each name is what the client provides, typed as the package types it, and a script splices it:
|
|
53
53
|
|
|
54
54
|
```ts
|
|
55
|
-
import {
|
|
56
|
-
|
|
57
|
-
const greet = createImport<() => string>({
|
|
58
|
-
name: "greet",
|
|
59
|
-
from: "my-native-module",
|
|
60
|
-
version: "^1.0.0",
|
|
61
|
-
});
|
|
55
|
+
import { greet } from "my-native-module" with { backtick: "client" };
|
|
62
56
|
// In a script: cs`<p>{$greet()}</p>`
|
|
63
57
|
```
|
|
64
58
|
|
|
65
|
-
The client must provide
|
|
59
|
+
The package must be installed where the server's file finds it, for its types and its version. The client must provide it, and the server must list it in the bundler's `modules` at that version or a later one of the same major. The compiler writes each name as what `createImport({ name, from, version })` makes, which a hand-written import can call too.
|
|
66
60
|
|
|
67
61
|
## API
|
|
68
62
|
|
|
@@ -73,17 +67,22 @@ The client must provide that package, and the server must list it in the bundler
|
|
|
73
67
|
- `Spliced<T>`: what a spliceable value becomes on the client (`Client<U>` becomes `U`; arrays and objects map member by member).
|
|
74
68
|
- `createImport<T>({ name, from, version })`: an export of a module the client provides, as a value a script can splice.
|
|
75
69
|
- `isClientImport`, `ClientImport`: its check and type.
|
|
70
|
+
- `Bundle<T>`: a bundle's code, branded with what it runs to. `bundler.build(…).generate(…)` writes one.
|
|
71
|
+
- `execRoot(bundle, moduleMap)`, from `@backtickjs/core/cjs`: in a React Native app, runs the CommonJS bundle it starts from to its value, as a promise, with `require` answered from `moduleMap`, a `ModuleMap`: the modules the app provides, by specifier.
|
|
72
|
+
- `execRoot(bundle)`, from `@backtickjs/core/es`: in a web page, imports the ES bundle it starts from and resolves to its value, the page's import map answering its imports. Each format has its own entry, so a React Native app's bundler never reads the ES one's `import()`.
|
|
73
|
+
- `exec`: in a script, `$exec(bundle)` runs another bundle as the script's own was run, with the same modules, to a promise of its value.
|
|
74
|
+
- `isClientExec`, `ClientExec`: its check and type.
|
|
76
75
|
- `SplicesAs<T>`: `T` as a splice checks it, member by member. Used by the bundler to check its input.
|
|
77
|
-
- `createJsxElement`, `isJsxElement`, `JsxElement`, `JsxElementOf`, `JsxElementTypeOf`: what a server-side JSX tag evaluates to before bundling. Used by
|
|
78
|
-
- `create`, `isClientScript`, `ClientScript`, `
|
|
76
|
+
- `createJsxElement`, `isJsxElement`, `JsxElement`, `JsxElementOf`, `JsxElementTypeOf`: what a server-side JSX tag evaluates to before bundling. Used by the JSX packages' runtimes.
|
|
77
|
+
- `create`, `isClientScript`, `ClientScript`, `ScriptModule`, `Param`: the compiled form of a script, written by the compiler and read by the bundler. Not for application code.
|
|
79
78
|
|
|
80
79
|
## Related
|
|
81
80
|
|
|
82
81
|
- [`@backtickjs/bundler`](https://github.com/backtickjs/backtick/tree/main/packages/bundler): runs server components and writes the bundle
|
|
83
|
-
- [`@backtickjs/react`](https://github.com/backtickjs/backtick/tree/main/
|
|
82
|
+
- [`@backtickjs/jsx-react`](https://github.com/backtickjs/backtick/tree/main/packages/jsx-react), [`@backtickjs/jsx-solid`](https://github.com/backtickjs/backtick/tree/main/packages/jsx-solid): each framework's JSX
|
|
84
83
|
- [`@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
|
|
85
84
|
- [`@backtickjs/tsc`](https://github.com/backtickjs/backtick/tree/main/packages/tsc): type-checks scripts and their splices
|
|
86
|
-
- [Thinking in Backtick](https://backtickjs.com/docs/thinking-in-backtick), [Scripts in depth](https://backtickjs.com/docs/scripts-in-depth)
|
|
85
|
+
- [Thinking in Backtick](https://backtickjs.com/docs/thinking-in-backtick/), [Scripts in depth](https://backtickjs.com/docs/scripts-in-depth/)
|
|
87
86
|
|
|
88
87
|
## License
|
|
89
88
|
|
package/dist/Bundle.d.ts
ADDED
package/dist/Bundle.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Bundle.js","sourceRoot":"","sources":["../src/Bundle.ts"],"names":[],"mappings":""}
|
package/dist/ClientImport.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { Client } from "./Client.js";
|
|
2
2
|
/**
|
|
3
3
|
* An export of a module the client provides, held as a value: what a script
|
|
4
|
-
* splices to use it, and what a bundle imports it by.
|
|
4
|
+
* splices to use it, and what a bundle imports it by. The compiler writes one
|
|
5
|
+
* per name of `import { View } from "react-native" with { backtick: "client" }`.
|
|
5
6
|
*/
|
|
6
7
|
export interface ClientImport<T> extends Client<T> {
|
|
7
8
|
readonly "@backtickjs": "ClientImport";
|
package/dist/ClientImport.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ClientImport.js","sourceRoot":"","sources":["../src/ClientImport.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ClientImport.js","sourceRoot":"","sources":["../src/ClientImport.ts"],"names":[],"mappings":"AAiBA,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,4EAA4E;AAC5E,4EAA4E;AAC5E,MAAM,UAAU,YAAY,CAAI,EAC9B,IAAI,EACJ,IAAI,EACJ,OAAO,GAKR;IACC,OAAO;QACL,aAAa,EAAE,cAAc;QAC7B,IAAI;QACJ,IAAI;QACJ,OAAO;KACsB,CAAC;AAClC,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Client } from "./Client.js";
|
|
2
|
+
/**
|
|
3
|
+
* A module's exports as client imports, each typed by its declaration: what
|
|
4
|
+
* the editor checks the names of a client import against.
|
|
5
|
+
*/
|
|
6
|
+
export type ClientModule<M> = {
|
|
7
|
+
readonly [Name in keyof M]: Client<M[Name]>;
|
|
8
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ClientModule.js","sourceRoot":"","sources":["../src/ClientModule.ts"],"names":[],"mappings":""}
|
package/dist/ClientScript.d.ts
CHANGED
|
@@ -6,19 +6,19 @@ export type Param = {
|
|
|
6
6
|
kind: "capture";
|
|
7
7
|
key: string;
|
|
8
8
|
};
|
|
9
|
-
export interface
|
|
9
|
+
export interface ScriptModule {
|
|
10
10
|
readonly id: string;
|
|
11
11
|
readonly code: string;
|
|
12
12
|
readonly map: string;
|
|
13
13
|
readonly dependencies: readonly string[];
|
|
14
14
|
readonly params: readonly Param[];
|
|
15
|
-
readonly kind: "function" | "expression" | "block";
|
|
15
|
+
readonly kind: "function" | "class" | "expression" | "block";
|
|
16
16
|
}
|
|
17
17
|
export interface ClientScript {
|
|
18
18
|
readonly "@backtickjs": "ClientScript";
|
|
19
|
-
readonly module:
|
|
19
|
+
readonly module: ScriptModule;
|
|
20
20
|
readonly args: readonly Spliceable[];
|
|
21
21
|
readonly once?: true;
|
|
22
22
|
}
|
|
23
23
|
export declare function isClientScript(value: unknown): value is ClientScript;
|
|
24
|
-
export declare function create(module:
|
|
24
|
+
export declare function create(module: ScriptModule, 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":"AA6CA,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/JsxElement.d.ts
CHANGED
|
@@ -2,8 +2,8 @@ import type { Client } from "./Client.js";
|
|
|
2
2
|
/**
|
|
3
3
|
* What a JSX tag evaluates to on the server, before bundling resolves it: the
|
|
4
4
|
* tag and its props, which are the server's own. What a drawing is, is the
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* JSX package's; this is what the bundler expands. A client value, of the type
|
|
6
|
+
* its JSX package names (Solid's: its element), so a host may hand one over wherever
|
|
7
7
|
* a drawing may stand.
|
|
8
8
|
*/
|
|
9
9
|
export interface JsxElement extends Client<unknown> {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a bundle may import, by specifier: each the module the app imported
|
|
3
|
+
* for it. What a web page's import map does, for an app, with modules for
|
|
4
|
+
* values instead of URLs:
|
|
5
|
+
*
|
|
6
|
+
* import * as React from "react";
|
|
7
|
+
* import * as JSXRuntime from "react/jsx-runtime";
|
|
8
|
+
* import * as ReactNative from "react-native";
|
|
9
|
+
*
|
|
10
|
+
* export const moduleMap = {
|
|
11
|
+
* react: React,
|
|
12
|
+
* "react/jsx-runtime": JSXRuntime,
|
|
13
|
+
* "react-native": ReactNative,
|
|
14
|
+
* };
|
|
15
|
+
*/
|
|
16
|
+
export type ModuleMap = Readonly<Record<string, unknown>>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ModuleMap.js","sourceRoot":"","sources":["../src/ModuleMap.ts"],"names":[],"mappings":""}
|
package/dist/Spliceable.d.ts
CHANGED
|
@@ -20,6 +20,6 @@ export type SplicesAs<T> = [T] extends [Spliceable] ? T : T extends (...args: ne
|
|
|
20
20
|
};
|
|
21
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
22
|
-readonly [K in keyof T]: Spliced<T[K]>;
|
|
23
|
-
} : T extends object ? {
|
|
23
|
+
} : T extends string | number | bigint | boolean ? T : T extends object ? {
|
|
24
24
|
-readonly [K in keyof T]: Spliced<T[K]>;
|
|
25
25
|
} : T;
|
package/dist/cjs.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Bundle } from "./Bundle.js";
|
|
2
|
+
import type { ModuleMap } from "./ModuleMap.js";
|
|
3
|
+
export type { ModuleMap };
|
|
4
|
+
/**
|
|
5
|
+
* Executes a CommonJS bundle an app starts from, the one nothing else runs,
|
|
6
|
+
* to its value, with `require` answered from `moduleMap`: how a React Native
|
|
7
|
+
* app runs what its server answers. The bundles it runs in turn, with `exec`,
|
|
8
|
+
* get the same modules. Fetching the bundle, and when, is the app's. The
|
|
9
|
+
* entry an app imports is its format: a web page imports
|
|
10
|
+
* `@backtickjs/core/es`.
|
|
11
|
+
*
|
|
12
|
+
* const screen = await execRoot(bundle, moduleMap);
|
|
13
|
+
*/
|
|
14
|
+
export declare function execRoot<T>(bundle: Bundle<T>, moduleMap: ModuleMap): Promise<Awaited<T>>;
|
package/dist/cjs.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Executes a CommonJS bundle an app starts from, the one nothing else runs,
|
|
3
|
+
* to its value, with `require` answered from `moduleMap`: how a React Native
|
|
4
|
+
* app runs what its server answers. The bundles it runs in turn, with `exec`,
|
|
5
|
+
* get the same modules. Fetching the bundle, and when, is the app's. The
|
|
6
|
+
* entry an app imports is its format: a web page imports
|
|
7
|
+
* `@backtickjs/core/es`.
|
|
8
|
+
*
|
|
9
|
+
* const screen = await execRoot(bundle, moduleMap);
|
|
10
|
+
*/
|
|
11
|
+
export async function execRoot(bundle, moduleMap) {
|
|
12
|
+
const require = (specifier) => {
|
|
13
|
+
if (!Object.hasOwn(moduleMap, specifier)) {
|
|
14
|
+
throw new Error(`The bundle requires "${specifier}", which this app doesn't provide.`);
|
|
15
|
+
}
|
|
16
|
+
return moduleMap[specifier];
|
|
17
|
+
};
|
|
18
|
+
const module = { exports: {} };
|
|
19
|
+
new Function("module", "exports", "require", bundle)(module, module.exports, require);
|
|
20
|
+
return module.exports;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=cjs.js.map
|
package/dist/cjs.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cjs.js","sourceRoot":"","sources":["../src/cjs.ts"],"names":[],"mappings":"AAKA;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,MAAiB,EACjB,SAAoB;IAEpB,MAAM,OAAO,GAAG,CAAC,SAAiB,EAAW,EAAE;QAC7C,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,SAAS,CAAC,EAAE,CAAC;YACzC,MAAM,IAAI,KAAK,CACb,wBAAwB,SAAS,oCAAoC,CACtE,CAAC;QACJ,CAAC;QACD,OAAO,SAAS,CAAC,SAAS,CAAC,CAAC;IAC9B,CAAC,CAAC;IACF,MAAM,MAAM,GAAG,EAAE,OAAO,EAAE,EAAa,EAAE,CAAC;IAC1C,IAAI,QAAQ,CAAC,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,CAAC,CAClD,MAAM,EACN,MAAM,CAAC,OAAO,EACd,OAAO,CACR,CAAC;IACF,OAAO,MAAM,CAAC,OAAqB,CAAC;AACtC,CAAC"}
|
package/dist/es.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Bundle } from "./Bundle.js";
|
|
2
|
+
/**
|
|
3
|
+
* Executes an ES bundle a web page starts from, the one nothing else runs, to
|
|
4
|
+
* its value: imported from a `data:` URL, the page's import map answering its
|
|
5
|
+
* imports, and those of the bundles it runs in turn with `exec`. Apart from
|
|
6
|
+
* `@backtickjs/core/cjs`, so a React Native app never reads its `import()`:
|
|
7
|
+
* Metro rejects one of anything but a string literal, and Hermes has none.
|
|
8
|
+
*
|
|
9
|
+
* const screen = await execRoot(bundle);
|
|
10
|
+
*/
|
|
11
|
+
export declare function execRoot<T>(bundle: Bundle<T>): Promise<Awaited<T>>;
|
package/dist/es.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Executes an ES bundle a web page starts from, the one nothing else runs, to
|
|
3
|
+
* its value: imported from a `data:` URL, the page's import map answering its
|
|
4
|
+
* imports, and those of the bundles it runs in turn with `exec`. Apart from
|
|
5
|
+
* `@backtickjs/core/cjs`, so a React Native app never reads its `import()`:
|
|
6
|
+
* Metro rejects one of anything but a string literal, and Hermes has none.
|
|
7
|
+
*
|
|
8
|
+
* const screen = await execRoot(bundle);
|
|
9
|
+
*/
|
|
10
|
+
export async function execRoot(bundle) {
|
|
11
|
+
const module = (await import("data:text/javascript," + encodeURIComponent(bundle)));
|
|
12
|
+
return module.default;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=es.js.map
|
package/dist/es.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"es.js","sourceRoot":"","sources":["../src/es.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAI,MAAiB;IACjD,MAAM,MAAM,GAAG,CAAC,MAAM,MAAM,CAC1B,uBAAuB,GAAG,kBAAkB,CAAC,MAAM,CAAC,CACrD,CAA4B,CAAC;IAC9B,OAAO,MAAM,CAAC,OAAO,CAAC;AACxB,CAAC"}
|
package/dist/exec.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Bundle } from "./Bundle.js";
|
|
2
|
+
import type { Client } from "./Client.js";
|
|
3
|
+
/**
|
|
4
|
+
* Executes a bundle on the client, as the bundle reading it was run, with the
|
|
5
|
+
* same modules, and resolves to its value: what a script fetched at runtime
|
|
6
|
+
* from the server, which the bundler couldn't see. As CommonJS, a function of
|
|
7
|
+
* `require`, handed the bundle's own; as an ES module, imported from a `data:`
|
|
8
|
+
* URL, which the page's import map answers.
|
|
9
|
+
*/
|
|
10
|
+
export interface ClientExec extends Client<(<T>(bundle: Bundle<T>) => Promise<Awaited<T>>)> {
|
|
11
|
+
readonly "@backtickjs": "ClientExec";
|
|
12
|
+
}
|
|
13
|
+
export declare const exec: Client<(<T>(bundle: Bundle<T>) => Promise<Awaited<T>>)>;
|
|
14
|
+
export declare function isClientExec(value: unknown): value is ClientExec;
|
package/dist/exec.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const exec = {
|
|
2
|
+
"@backtickjs": "ClientExec",
|
|
3
|
+
};
|
|
4
|
+
export function isClientExec(value) {
|
|
5
|
+
return (typeof value === "object" &&
|
|
6
|
+
value !== null &&
|
|
7
|
+
"@backtickjs" in value &&
|
|
8
|
+
value["@backtickjs"] === "ClientExec");
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=exec.js.map
|
package/dist/exec.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exec.js","sourceRoot":"","sources":["../src/exec.ts"],"names":[],"mappings":"AAgBA,MAAM,CAAC,MAAM,IAAI,GAA0D;IACzE,aAAa,EAAE,YAAY;CACH,CAAC;AAE3B,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACd,aAAa,IAAI,KAAK;QACtB,KAAK,CAAC,aAAa,CAAC,KAAK,YAAY,CACtC,CAAC;AACJ,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -7,5 +7,8 @@ export type { Client } from "./Client.js";
|
|
|
7
7
|
export { once } from "./once.js";
|
|
8
8
|
export type { Spliceable, SplicesAs, Spliced } from "./Spliceable.js";
|
|
9
9
|
export { createImport, isClientImport, type ClientImport, } from "./ClientImport.js";
|
|
10
|
+
export type { ClientModule } from "./ClientModule.js";
|
|
11
|
+
export type { Bundle } from "./Bundle.js";
|
|
12
|
+
export { exec, isClientExec, type ClientExec } from "./exec.js";
|
|
10
13
|
export { createJsxElement, isJsxElement, type JsxElement, type JsxElementOf, type JsxElementTypeOf, } from "./JsxElement.js";
|
|
11
|
-
export { create, type
|
|
14
|
+
export { create, type ScriptModule, type ClientScript, isClientScript, type Param, } from "./ClientScript.js";
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
export { cs } from "./cs.js";
|
|
8
8
|
export { once } from "./once.js";
|
|
9
9
|
export { createImport, isClientImport, } from "./ClientImport.js";
|
|
10
|
-
|
|
10
|
+
export { exec, isClientExec } from "./exec.js";
|
|
11
|
+
// A drawing as a host builds one: what a JSX package's runtime makes, and
|
|
11
12
|
// what the bundler expands.
|
|
12
13
|
export { createJsxElement, isJsxElement, } from "./JsxElement.js";
|
|
13
14
|
// What a template compiles to: the compiler writes one and the bundler reads
|
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,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;
|
|
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;AAM3B,OAAO,EAAE,IAAI,EAAE,YAAY,EAAmB,MAAM,WAAW,CAAC;AAEhE,0EAA0E;AAC1E,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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backtickjs/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "The cs tag for Backtick client scripts, and the types for the values that cross from server to client",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"backtick",
|
|
@@ -28,6 +28,14 @@
|
|
|
28
28
|
".": {
|
|
29
29
|
"types": "./dist/index.d.ts",
|
|
30
30
|
"default": "./dist/index.js"
|
|
31
|
+
},
|
|
32
|
+
"./cjs": {
|
|
33
|
+
"types": "./dist/cjs.d.ts",
|
|
34
|
+
"default": "./dist/cjs.js"
|
|
35
|
+
},
|
|
36
|
+
"./es": {
|
|
37
|
+
"types": "./dist/es.d.ts",
|
|
38
|
+
"default": "./dist/es.js"
|
|
31
39
|
}
|
|
32
40
|
},
|
|
33
41
|
"files": [
|
package/src/Bundle.ts
ADDED
package/src/ClientImport.ts
CHANGED
|
@@ -2,14 +2,16 @@ import type { Client } from "./Client.js";
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* An export of a module the client provides, held as a value: what a script
|
|
5
|
-
* splices to use it, and what a bundle imports it by.
|
|
5
|
+
* splices to use it, and what a bundle imports it by. The compiler writes one
|
|
6
|
+
* per name of `import { View } from "react-native" with { backtick: "client" }`.
|
|
6
7
|
*/
|
|
7
8
|
export interface ClientImport<T> extends Client<T> {
|
|
8
9
|
readonly "@backtickjs": "ClientImport";
|
|
10
|
+
// the export, or `*` for the module's namespace
|
|
9
11
|
readonly name: string;
|
|
10
12
|
// the specifier the client resolves
|
|
11
13
|
readonly from: string;
|
|
12
|
-
// the
|
|
14
|
+
// the exact version of `from`'s package it's typed against
|
|
13
15
|
readonly version: string;
|
|
14
16
|
}
|
|
15
17
|
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Client } from "./Client.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A module's exports as client imports, each typed by its declaration: what
|
|
5
|
+
* the editor checks the names of a client import against.
|
|
6
|
+
*/
|
|
7
|
+
export type ClientModule<M> = { readonly [Name in keyof M]: Client<M[Name]> };
|
package/src/ClientScript.ts
CHANGED
|
@@ -10,7 +10,7 @@ export type Param =
|
|
|
10
10
|
| { kind: "capture"; key: string };
|
|
11
11
|
|
|
12
12
|
// The compiled code of one `cs`, shared by every script it creates.
|
|
13
|
-
export interface
|
|
13
|
+
export interface ScriptModule {
|
|
14
14
|
// `<fileHash>:<line>:<column>` of the `cs`. The hash keeps ids from two
|
|
15
15
|
// files apart: they match only when the files are identical.
|
|
16
16
|
readonly id: string;
|
|
@@ -24,8 +24,9 @@ export interface ClientModule {
|
|
|
24
24
|
// `params[i]` is the function's `$splice<i>` or `$capture<i>`
|
|
25
25
|
readonly params: readonly Param[];
|
|
26
26
|
// What the script is written as: a function, `(props) => …`, which a splice
|
|
27
|
-
// reads as that function;
|
|
28
|
-
|
|
27
|
+
// reads as that function; a class, `class … {}`, one class for the bundle;
|
|
28
|
+
// or an expression or a block, which a splice runs.
|
|
29
|
+
readonly kind: "function" | "class" | "expression" | "block";
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
// One run of a `cs`: its module, and the values that run passes it. The rest
|
|
@@ -34,7 +35,7 @@ export interface ClientModule {
|
|
|
34
35
|
// flag reads the same.
|
|
35
36
|
export interface ClientScript {
|
|
36
37
|
readonly "@backtickjs": "ClientScript";
|
|
37
|
-
readonly module:
|
|
38
|
+
readonly module: ScriptModule;
|
|
38
39
|
// one per splice, in parameter order
|
|
39
40
|
readonly args: readonly Spliceable[];
|
|
40
41
|
// one value for the bundle, made at its top, rather than code run at each
|
|
@@ -52,7 +53,7 @@ export function isClientScript(value: unknown): value is ClientScript {
|
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
export function create(
|
|
55
|
-
module:
|
|
56
|
+
module: ScriptModule,
|
|
56
57
|
args: readonly Spliceable[],
|
|
57
58
|
): ClientScript {
|
|
58
59
|
return { "@backtickjs": "ClientScript", module, args };
|
package/src/JsxElement.ts
CHANGED
|
@@ -3,8 +3,8 @@ import type { Client } from "./Client.js";
|
|
|
3
3
|
/**
|
|
4
4
|
* What a JSX tag evaluates to on the server, before bundling resolves it: the
|
|
5
5
|
* tag and its props, which are the server's own. What a drawing is, is the
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* JSX package's; this is what the bundler expands. A client value, of the type
|
|
7
|
+
* its JSX package names (Solid's: its element), so a host may hand one over wherever
|
|
8
8
|
* a drawing may stand.
|
|
9
9
|
*/
|
|
10
10
|
export interface JsxElement extends Client<unknown> {
|
|
@@ -29,7 +29,7 @@ export function createJsxElement(
|
|
|
29
29
|
return { "@backtickjs": "JsxElement", type, props } as JsxElement;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
// What
|
|
32
|
+
// What a JSX package's namespace types its `Element` and `ElementType` as,
|
|
33
33
|
// given what its framework draws (Solid's `JSX.Element`, React's `ReactNode`).
|
|
34
34
|
// One namespace types both a host's JSX and a script's, so each type answers
|
|
35
35
|
// for both sides.
|
package/src/ModuleMap.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a bundle may import, by specifier: each the module the app imported
|
|
3
|
+
* for it. What a web page's import map does, for an app, with modules for
|
|
4
|
+
* values instead of URLs:
|
|
5
|
+
*
|
|
6
|
+
* import * as React from "react";
|
|
7
|
+
* import * as JSXRuntime from "react/jsx-runtime";
|
|
8
|
+
* import * as ReactNative from "react-native";
|
|
9
|
+
*
|
|
10
|
+
* export const moduleMap = {
|
|
11
|
+
* react: React,
|
|
12
|
+
* "react/jsx-runtime": JSXRuntime,
|
|
13
|
+
* "react-native": ReactNative,
|
|
14
|
+
* };
|
|
15
|
+
*/
|
|
16
|
+
export type ModuleMap = Readonly<Record<string, unknown>>;
|
package/src/Spliceable.ts
CHANGED
|
@@ -34,7 +34,7 @@ export type SplicesAs<T> = [T] extends [Spliceable]
|
|
|
34
34
|
// Client<U> -> U
|
|
35
35
|
// T[], [A, B] -> Spliced<T>[], [Spliced<A>, Spliced<B>]
|
|
36
36
|
// { k: T } -> { k: Spliced<T> }
|
|
37
|
-
// primitives -> unchanged
|
|
37
|
+
// primitives -> unchanged, branded ones too e.g. `Bundle<T>`
|
|
38
38
|
// A tuple maps homomorphically, which keeps it a tuple; any other array stays
|
|
39
39
|
// `Spliced<Item>[]`, which TypeScript defers, so `Spliceable[]` doesn't recurse.
|
|
40
40
|
export type Spliced<T> =
|
|
@@ -44,6 +44,8 @@ export type Spliced<T> =
|
|
|
44
44
|
? number extends T["length"]
|
|
45
45
|
? Spliced<Item>[]
|
|
46
46
|
: { -readonly [K in keyof T]: Spliced<T[K]> }
|
|
47
|
-
: T extends
|
|
48
|
-
?
|
|
49
|
-
: T
|
|
47
|
+
: T extends string | number | bigint | boolean
|
|
48
|
+
? T
|
|
49
|
+
: T extends object
|
|
50
|
+
? { -readonly [K in keyof T]: Spliced<T[K]> }
|
|
51
|
+
: T;
|
package/src/cjs.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Bundle } from "./Bundle.js";
|
|
2
|
+
import type { ModuleMap } from "./ModuleMap.js";
|
|
3
|
+
|
|
4
|
+
export type { ModuleMap };
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Executes a CommonJS bundle an app starts from, the one nothing else runs,
|
|
8
|
+
* to its value, with `require` answered from `moduleMap`: how a React Native
|
|
9
|
+
* app runs what its server answers. The bundles it runs in turn, with `exec`,
|
|
10
|
+
* get the same modules. Fetching the bundle, and when, is the app's. The
|
|
11
|
+
* entry an app imports is its format: a web page imports
|
|
12
|
+
* `@backtickjs/core/es`.
|
|
13
|
+
*
|
|
14
|
+
* const screen = await execRoot(bundle, moduleMap);
|
|
15
|
+
*/
|
|
16
|
+
export async function execRoot<T>(
|
|
17
|
+
bundle: Bundle<T>,
|
|
18
|
+
moduleMap: ModuleMap,
|
|
19
|
+
): Promise<Awaited<T>> {
|
|
20
|
+
const require = (specifier: string): unknown => {
|
|
21
|
+
if (!Object.hasOwn(moduleMap, specifier)) {
|
|
22
|
+
throw new Error(
|
|
23
|
+
`The bundle requires "${specifier}", which this app doesn't provide.`,
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
return moduleMap[specifier];
|
|
27
|
+
};
|
|
28
|
+
const module = { exports: {} as unknown };
|
|
29
|
+
new Function("module", "exports", "require", bundle)(
|
|
30
|
+
module,
|
|
31
|
+
module.exports,
|
|
32
|
+
require,
|
|
33
|
+
);
|
|
34
|
+
return module.exports as Awaited<T>;
|
|
35
|
+
}
|
package/src/es.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Bundle } from "./Bundle.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Executes an ES bundle a web page starts from, the one nothing else runs, to
|
|
5
|
+
* its value: imported from a `data:` URL, the page's import map answering its
|
|
6
|
+
* imports, and those of the bundles it runs in turn with `exec`. Apart from
|
|
7
|
+
* `@backtickjs/core/cjs`, so a React Native app never reads its `import()`:
|
|
8
|
+
* Metro rejects one of anything but a string literal, and Hermes has none.
|
|
9
|
+
*
|
|
10
|
+
* const screen = await execRoot(bundle);
|
|
11
|
+
*/
|
|
12
|
+
export async function execRoot<T>(bundle: Bundle<T>): Promise<Awaited<T>> {
|
|
13
|
+
const module = (await import(
|
|
14
|
+
"data:text/javascript," + encodeURIComponent(bundle)
|
|
15
|
+
)) as { default: Awaited<T> };
|
|
16
|
+
return module.default;
|
|
17
|
+
}
|
package/src/exec.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Bundle } from "./Bundle.js";
|
|
2
|
+
import type { Client } from "./Client.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Executes a bundle on the client, as the bundle reading it was run, with the
|
|
6
|
+
* same modules, and resolves to its value: what a script fetched at runtime
|
|
7
|
+
* from the server, which the bundler couldn't see. As CommonJS, a function of
|
|
8
|
+
* `require`, handed the bundle's own; as an ES module, imported from a `data:`
|
|
9
|
+
* URL, which the page's import map answers.
|
|
10
|
+
*/
|
|
11
|
+
export interface ClientExec extends Client<
|
|
12
|
+
<T>(bundle: Bundle<T>) => Promise<Awaited<T>>
|
|
13
|
+
> {
|
|
14
|
+
readonly "@backtickjs": "ClientExec";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const exec: Client<<T>(bundle: Bundle<T>) => Promise<Awaited<T>>> = {
|
|
18
|
+
"@backtickjs": "ClientExec",
|
|
19
|
+
} as unknown as ClientExec;
|
|
20
|
+
|
|
21
|
+
export function isClientExec(value: unknown): value is ClientExec {
|
|
22
|
+
return (
|
|
23
|
+
typeof value === "object" &&
|
|
24
|
+
value !== null &&
|
|
25
|
+
"@backtickjs" in value &&
|
|
26
|
+
value["@backtickjs"] === "ClientExec"
|
|
27
|
+
);
|
|
28
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -17,8 +17,14 @@ export {
|
|
|
17
17
|
isClientImport,
|
|
18
18
|
type ClientImport,
|
|
19
19
|
} from "./ClientImport.js";
|
|
20
|
+
export type { ClientModule } from "./ClientModule.js";
|
|
21
|
+
// A bundle's code, typed by what it runs to, and, in a script, executing
|
|
22
|
+
// another as this one was. The one an app starts from is executed by
|
|
23
|
+
// `@backtickjs/core/cjs` or `@backtickjs/core/es`.
|
|
24
|
+
export type { Bundle } from "./Bundle.js";
|
|
25
|
+
export { exec, isClientExec, type ClientExec } from "./exec.js";
|
|
20
26
|
|
|
21
|
-
// A drawing as a host builds one: what
|
|
27
|
+
// A drawing as a host builds one: what a JSX package's runtime makes, and
|
|
22
28
|
// what the bundler expands.
|
|
23
29
|
export {
|
|
24
30
|
createJsxElement,
|
|
@@ -32,7 +38,7 @@ export {
|
|
|
32
38
|
// it, so the shape is neither end's.
|
|
33
39
|
export {
|
|
34
40
|
create,
|
|
35
|
-
type
|
|
41
|
+
type ScriptModule,
|
|
36
42
|
type ClientScript,
|
|
37
43
|
isClientScript,
|
|
38
44
|
type Param,
|