@backtickjs/core 0.1.11 → 0.2.1

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 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`), an adapter for its framework (`@backtickjs/react`, `@backtickjs/react-native` or `@backtickjs/solid-js`) and `@backtickjs/tsc` for type checking. Called without the compiler, `cs` throws. `npx create-backtick-app@latest` sets all of this up.
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 "@backtickjs/react";
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 }) => {
@@ -42,27 +42,67 @@ A script every component shares, like a context or a store, is made once with `o
42
42
 
43
43
  ```tsx
44
44
  import { cs, once } from "@backtickjs/core";
45
- import { createContext, useContext } from "@backtickjs/react";
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 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:
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 { createImport } from "@backtickjs/core";
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 that package, and the server must list it in the bundler's `modules` at a version in that range.
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.
60
+
61
+ ## Every build in users' hands
62
+
63
+ A store build carries the modules it was built with, at their versions, for as long as it's installed, while your server moves on. Each build sends the modules it carries, and the server bundles its screens for exactly those: there's nothing to record before a build ships, and any version of your server can draw for any build.
64
+
65
+ 1. **The app's module map** offers what screens may require, each from a package in `package.json`'s dependencies. A platform that differs gets its own file, as Metro picks files: `client/moduleMap.web.ts` for the web.
66
+
67
+ 2. **The app's config carries its packages' versions.** `installedPackages`, from `@backtickjs/core/node`, reads the installed version of each of the app's dependencies from `node_modules`. In an Expo app, `app.config.ts` puts them in the config, which Expo embeds in the app:
68
+
69
+ ```ts
70
+ // app.config.ts
71
+ import { installedPackages } from "@backtickjs/core/node";
72
+ import type { ConfigContext, ExpoConfig } from "expo/config";
73
+
74
+ export default ({ config, projectRoot }: ConfigContext): ExpoConfig => ({
75
+ ...(config as ExpoConfig),
76
+ extra: {
77
+ ...config.extra,
78
+ backtick: { packages: installedPackages(projectRoot) },
79
+ },
80
+ });
81
+ ```
82
+
83
+ 3. **The app sends its modules** however it reaches its server: a header, a GraphQL variable. `modulesOf` gives each module in the map its package's version, and throws as the app starts without them, or for a module from a package that isn't a dependency:
84
+
85
+ ```ts
86
+ import { modulesOf } from "@backtickjs/core";
87
+
88
+ const modules = JSON.stringify(
89
+ modulesOf(moduleMap, Constants.expoConfig?.extra?.backtick?.packages),
90
+ );
91
+ fetch(url, { headers: { "x-backtick-modules": modules } });
92
+ ```
93
+
94
+ 4. **The server bundles for them:**
95
+
96
+ ```ts
97
+ const modules = JSON.parse(context.req.header("x-backtick-modules") ?? "{}");
98
+ const bundle = await bundler.build({ input, modules });
99
+ ```
100
+
101
+ The bundler refuses modules that aren't `{ "specifier": "version" }`, and a screen that imports a module the build doesn't carry, or at a version it can't run. To draw differently for builds that lack a module, or to ask old ones to update, check with `supports` from [`@backtickjs/bundler`](https://github.com/backtickjs/backtick/tree/main/packages/bundler): `supports(modules, "react-native", ">=0.86")`.
102
+
103
+ In development, a reload keeps the app config the app was opened with, and its versions with it: after upgrading a package, close the app and open it again.
104
+
105
+ A test draws a screen with a module map of its own, stand-ins included; its modules are that map's, at the versions the app installs: `modulesOf(moduleMap, installedPackages(projectRoot))`.
66
106
 
67
107
  ## API
68
108
 
@@ -76,16 +116,18 @@ The client must provide that package, and the server must list it in the bundler
76
116
  - `Bundle<T>`: a bundle's code, branded with what it runs to. `bundler.build(…).generate(…)` writes one.
77
117
  - `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.
78
118
  - `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()`.
119
+ - `modulesOf(moduleMap, packages)`: the modules `moduleMap`, a `ModuleMap`, offers, each at its package's version in `packages`, for the app to send its server.
120
+ - `installedPackages(projectRoot)`, from `@backtickjs/core/node`: the installed version of each of the app's dependencies, read from `node_modules`. Node only.
79
121
  - `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.
80
122
  - `isClientExec`, `ClientExec`: its check and type.
81
123
  - `SplicesAs<T>`: `T` as a splice checks it, member by member. Used by the bundler to check its input.
82
- - `createJsxElement`, `isJsxElement`, `JsxElement`, `JsxElementOf`, `JsxElementTypeOf`: what a server-side JSX tag evaluates to before bundling. Used by adapters' JSX runtimes.
83
- - `create`, `isClientScript`, `ClientScript`, `ClientModule`, `Param`: the compiled form of a script, written by the compiler and read by the bundler. Not for application code.
124
+ - `createJsxElement`, `isJsxElement`, `JsxElement`, `JsxElementOf`, `JsxElementTypeOf`: what a server-side JSX tag evaluates to before bundling. Used by the JSX packages' runtimes.
125
+ - `create`, `isClientScript`, `ClientScript`, `ScriptModule`, `Param`: the compiled form of a script, written by the compiler and read by the bundler. Not for application code.
84
126
 
85
127
  ## Related
86
128
 
87
129
  - [`@backtickjs/bundler`](https://github.com/backtickjs/backtick/tree/main/packages/bundler): runs server components and writes the bundle
88
- - [`@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
130
+ - [`@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
89
131
  - [`@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
90
132
  - [`@backtickjs/tsc`](https://github.com/backtickjs/backtick/tree/main/packages/tsc): type-checks scripts and their splices
91
133
  - [Thinking in Backtick](https://backtickjs.com/docs/thinking-in-backtick/), [Scripts in depth](https://backtickjs.com/docs/scripts-in-depth/)
@@ -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";
@@ -1 +1 @@
1
- {"version":3,"file":"ClientImport.js","sourceRoot":"","sources":["../src/ClientImport.ts"],"names":[],"mappings":"AAeA,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"}
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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ClientModule.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ClientModule.js","sourceRoot":"","sources":["../src/ClientModule.ts"],"names":[],"mappings":""}
@@ -6,7 +6,7 @@ export type Param = {
6
6
  kind: "capture";
7
7
  key: string;
8
8
  };
9
- export interface ClientModule {
9
+ export interface ScriptModule {
10
10
  readonly id: string;
11
11
  readonly code: string;
12
12
  readonly map: string;
@@ -16,9 +16,9 @@ export interface ClientModule {
16
16
  }
17
17
  export interface ClientScript {
18
18
  readonly "@backtickjs": "ClientScript";
19
- readonly module: ClientModule;
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: ClientModule, args: readonly Spliceable[]): ClientScript;
24
+ export declare function create(module: ScriptModule, args: readonly Spliceable[]): ClientScript;
@@ -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
- * adapter's; this is what the bundler expands. A client value, of the type its
6
- * adapter names (Solid's: its element), so a host may hand one over wherever
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> {
package/dist/cjs.d.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import type { Bundle } from "./Bundle.js";
2
- import type { ModuleMap } from "./ModuleMap.js";
3
- export type { ModuleMap };
2
+ import type { ModuleMap } from "./modulesOf.js";
4
3
  /**
5
4
  * Executes a CommonJS bundle an app starts from, the one nothing else runs,
6
5
  * to its value, with `require` answered from `moduleMap`: how a React Native
package/dist/cjs.js.map CHANGED
@@ -1 +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"}
1
+ {"version":3,"file":"cjs.js","sourceRoot":"","sources":["../src/cjs.ts"],"names":[],"mappings":"AAGA;;;;;;;;;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/index.d.ts CHANGED
@@ -7,7 +7,9 @@ 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";
10
11
  export type { Bundle } from "./Bundle.js";
11
12
  export { exec, isClientExec, type ClientExec } from "./exec.js";
12
13
  export { createJsxElement, isJsxElement, type JsxElement, type JsxElementOf, type JsxElementTypeOf, } from "./JsxElement.js";
13
- export { create, type ClientModule, type ClientScript, isClientScript, type Param, } from "./ClientScript.js";
14
+ export { create, type ScriptModule, type ClientScript, isClientScript, type Param, } from "./ClientScript.js";
15
+ export { modulesOf, type ModuleMap } from "./modulesOf.js";
package/dist/index.js CHANGED
@@ -8,10 +8,13 @@ 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 an adapter's JSX runtime makes, and
11
+ // A drawing as a host builds one: what a JSX package's runtime makes, and
12
12
  // what the bundler expands.
13
13
  export { createJsxElement, isJsxElement, } from "./JsxElement.js";
14
14
  // What a template compiles to: the compiler writes one and the bundler reads
15
15
  // it, so the shape is neither end's.
16
16
  export { create, isClientScript, } from "./ClientScript.js";
17
+ // What an app provides bundles, and what it sends its server for them: the
18
+ // modules it offers, at its packages' versions.
19
+ export { modulesOf } from "./modulesOf.js";
17
20
  //# sourceMappingURL=index.js.map
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;AAK3B,OAAO,EAAE,IAAI,EAAE,YAAY,EAAmB,MAAM,WAAW,CAAC;AAEhE,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"}
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;AAE3B,2EAA2E;AAC3E,gDAAgD;AAChD,OAAO,EAAE,SAAS,EAAkB,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,30 @@
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>>;
17
+ /**
18
+ * The modules `moduleMap` offers, each at the version in `packages` of the
19
+ * package it comes from: what the app sends however it reaches its server, a
20
+ * header, a GraphQL variable, for the server to bundle screens it can run.
21
+ * An Expo app passes them from its config, where `app.config.ts` puts
22
+ * `installedPackages(projectRoot)`. Throws as the app starts without them, or
23
+ * for a module from a package that isn't a dependency.
24
+ *
25
+ * const modules = modulesOf(
26
+ * moduleMap,
27
+ * Constants.expoConfig?.extra?.backtick?.packages,
28
+ * );
29
+ */
30
+ export declare function modulesOf(moduleMap: ModuleMap, packages: Readonly<Record<string, string>> | null | undefined): Record<string, string>;
@@ -0,0 +1,31 @@
1
+ // The package a module specifier is in: `react` for `react/jsx-runtime`.
2
+ function packageOf(specifier) {
3
+ const segments = specifier.split("/");
4
+ return segments.slice(0, specifier.startsWith("@") ? 2 : 1).join("/");
5
+ }
6
+ /**
7
+ * The modules `moduleMap` offers, each at the version in `packages` of the
8
+ * package it comes from: what the app sends however it reaches its server, a
9
+ * header, a GraphQL variable, for the server to bundle screens it can run.
10
+ * An Expo app passes them from its config, where `app.config.ts` puts
11
+ * `installedPackages(projectRoot)`. Throws as the app starts without them, or
12
+ * for a module from a package that isn't a dependency.
13
+ *
14
+ * const modules = modulesOf(
15
+ * moduleMap,
16
+ * Constants.expoConfig?.extra?.backtick?.packages,
17
+ * );
18
+ */
19
+ export function modulesOf(moduleMap, packages) {
20
+ if (packages == null) {
21
+ throw new Error("No packages: does app.config.ts put installedPackages(projectRoot) in the config's extra?");
22
+ }
23
+ return Object.fromEntries(Object.keys(moduleMap).map((specifier) => {
24
+ const version = packages[packageOf(specifier)];
25
+ if (version === undefined) {
26
+ throw new Error(`The module map offers ${JSON.stringify(specifier)}, but ${packageOf(specifier)} isn't in package.json's dependencies.`);
27
+ }
28
+ return [specifier, version];
29
+ }));
30
+ }
31
+ //# sourceMappingURL=modulesOf.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"modulesOf.js","sourceRoot":"","sources":["../src/modulesOf.ts"],"names":[],"mappings":"AAiBA,yEAAyE;AACzE,SAAS,SAAS,CAAC,SAAiB;IAClC,MAAM,QAAQ,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACtC,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACxE,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,SAAS,CACvB,SAAoB,EACpB,QAA6D;IAE7D,IAAI,QAAQ,IAAI,IAAI,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CACb,2FAA2F,CAC5F,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE;QACvC,MAAM,OAAO,GAAG,QAAQ,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC;QAC/C,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC1B,MAAM,IAAI,KAAK,CACb,yBAAyB,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,SAAS,SAAS,CAAC,SAAS,CAAC,wCAAwC,CACxH,CAAC;QACJ,CAAC;QACD,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAC9B,CAAC,CAAC,CACH,CAAC;AACJ,CAAC"}
package/dist/node.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The installed version of each of the app's dependencies, at `projectRoot`:
3
+ * what a build made from it now carries there, for `modulesOf`. Node only: an
4
+ * Expo app calls it from `app.config.ts`, and passes it on in the config.
5
+ *
6
+ * export default ({ config, projectRoot }: ConfigContext) => ({
7
+ * ...config,
8
+ * extra: {
9
+ * ...config.extra,
10
+ * backtick: { packages: installedPackages(projectRoot) },
11
+ * },
12
+ * });
13
+ */
14
+ export declare function installedPackages(projectRoot: string): Record<string, string>;
package/dist/node.js ADDED
@@ -0,0 +1,35 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ // Where a package is installed for the app at `projectRoot`, as Node finds
4
+ // it: in its node_modules, or the nearest above, as a workspace hoists it.
5
+ function installed(projectRoot, name) {
6
+ for (let directory = projectRoot;; directory = dirname(directory)) {
7
+ const file = join(directory, "node_modules", name, "package.json");
8
+ if (existsSync(file))
9
+ return file;
10
+ if (dirname(directory) === directory) {
11
+ throw new Error(`${projectRoot}: ${name} isn't installed`);
12
+ }
13
+ }
14
+ }
15
+ /**
16
+ * The installed version of each of the app's dependencies, at `projectRoot`:
17
+ * what a build made from it now carries there, for `modulesOf`. Node only: an
18
+ * Expo app calls it from `app.config.ts`, and passes it on in the config.
19
+ *
20
+ * export default ({ config, projectRoot }: ConfigContext) => ({
21
+ * ...config,
22
+ * extra: {
23
+ * ...config.extra,
24
+ * backtick: { packages: installedPackages(projectRoot) },
25
+ * },
26
+ * });
27
+ */
28
+ export function installedPackages(projectRoot) {
29
+ const { dependencies = {} } = JSON.parse(readFileSync(join(projectRoot, "package.json"), "utf8"));
30
+ return Object.fromEntries(Object.keys(dependencies).map((name) => [
31
+ name,
32
+ JSON.parse(readFileSync(installed(projectRoot, name), "utf8")).version,
33
+ ]));
34
+ }
35
+ //# sourceMappingURL=node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.js","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAE1C,2EAA2E;AAC3E,2EAA2E;AAC3E,SAAS,SAAS,CAAC,WAAmB,EAAE,IAAY;IAClD,KAAK,IAAI,SAAS,GAAG,WAAW,GAAI,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;QACnE,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,EAAE,cAAc,EAAE,IAAI,EAAE,cAAc,CAAC,CAAC;QACnE,IAAI,UAAU,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC;QAClC,IAAI,OAAO,CAAC,SAAS,CAAC,KAAK,SAAS,EAAE,CAAC;YACrC,MAAM,IAAI,KAAK,CAAC,GAAG,WAAW,KAAK,IAAI,kBAAkB,CAAC,CAAC;QAC7D,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,iBAAiB,CAAC,WAAmB;IACnD,MAAM,EAAE,YAAY,GAAG,EAAE,EAAE,GAAG,IAAI,CAAC,KAAK,CACtC,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CACX,CAAC;IAC/C,OAAO,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;QACtC,IAAI;QAEF,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,SAAS,CAAC,WAAW,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,CAG9D,CAAC,OAAO;KACV,CAAC,CACH,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backtickjs/core",
3
- "version": "0.1.11",
3
+ "version": "0.2.1",
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",
@@ -36,6 +36,10 @@
36
36
  "./es": {
37
37
  "types": "./dist/es.d.ts",
38
38
  "default": "./dist/es.js"
39
+ },
40
+ "./node": {
41
+ "types": "./dist/node.d.ts",
42
+ "default": "./dist/node.js"
39
43
  }
40
44
  },
41
45
  "files": [
@@ -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 versions of `from`'s package it works with, a semver range
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]> };
@@ -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 ClientModule {
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;
@@ -35,7 +35,7 @@ export interface ClientModule {
35
35
  // flag reads the same.
36
36
  export interface ClientScript {
37
37
  readonly "@backtickjs": "ClientScript";
38
- readonly module: ClientModule;
38
+ readonly module: ScriptModule;
39
39
  // one per splice, in parameter order
40
40
  readonly args: readonly Spliceable[];
41
41
  // one value for the bundle, made at its top, rather than code run at each
@@ -53,7 +53,7 @@ export function isClientScript(value: unknown): value is ClientScript {
53
53
  }
54
54
 
55
55
  export function create(
56
- module: ClientModule,
56
+ module: ScriptModule,
57
57
  args: readonly Spliceable[],
58
58
  ): ClientScript {
59
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
- * adapter's; this is what the bundler expands. A client value, of the type its
7
- * adapter names (Solid's: its element), so a host may hand one over wherever
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 an adapter's JSX namespace types its `Element` and `ElementType` as,
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/cjs.ts CHANGED
@@ -1,7 +1,5 @@
1
1
  import type { Bundle } from "./Bundle.js";
2
- import type { ModuleMap } from "./ModuleMap.js";
3
-
4
- export type { ModuleMap };
2
+ import type { ModuleMap } from "./modulesOf.js";
5
3
 
6
4
  /**
7
5
  * Executes a CommonJS bundle an app starts from, the one nothing else runs,
package/src/index.ts CHANGED
@@ -17,13 +17,14 @@ export {
17
17
  isClientImport,
18
18
  type ClientImport,
19
19
  } from "./ClientImport.js";
20
+ export type { ClientModule } from "./ClientModule.js";
20
21
  // A bundle's code, typed by what it runs to, and, in a script, executing
21
22
  // another as this one was. The one an app starts from is executed by
22
23
  // `@backtickjs/core/cjs` or `@backtickjs/core/es`.
23
24
  export type { Bundle } from "./Bundle.js";
24
25
  export { exec, isClientExec, type ClientExec } from "./exec.js";
25
26
 
26
- // A drawing as a host builds one: what an adapter's JSX runtime makes, and
27
+ // A drawing as a host builds one: what a JSX package's runtime makes, and
27
28
  // what the bundler expands.
28
29
  export {
29
30
  createJsxElement,
@@ -37,8 +38,12 @@ export {
37
38
  // it, so the shape is neither end's.
38
39
  export {
39
40
  create,
40
- type ClientModule,
41
+ type ScriptModule,
41
42
  type ClientScript,
42
43
  isClientScript,
43
44
  type Param,
44
45
  } from "./ClientScript.js";
46
+
47
+ // What an app provides bundles, and what it sends its server for them: the
48
+ // modules it offers, at its packages' versions.
49
+ export { modulesOf, type ModuleMap } from "./modulesOf.js";
@@ -0,0 +1,57 @@
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>>;
17
+
18
+ // The package a module specifier is in: `react` for `react/jsx-runtime`.
19
+ function packageOf(specifier: string): string {
20
+ const segments = specifier.split("/");
21
+ return segments.slice(0, specifier.startsWith("@") ? 2 : 1).join("/");
22
+ }
23
+
24
+ /**
25
+ * The modules `moduleMap` offers, each at the version in `packages` of the
26
+ * package it comes from: what the app sends however it reaches its server, a
27
+ * header, a GraphQL variable, for the server to bundle screens it can run.
28
+ * An Expo app passes them from its config, where `app.config.ts` puts
29
+ * `installedPackages(projectRoot)`. Throws as the app starts without them, or
30
+ * for a module from a package that isn't a dependency.
31
+ *
32
+ * const modules = modulesOf(
33
+ * moduleMap,
34
+ * Constants.expoConfig?.extra?.backtick?.packages,
35
+ * );
36
+ */
37
+ export function modulesOf(
38
+ moduleMap: ModuleMap,
39
+ packages: Readonly<Record<string, string>> | null | undefined,
40
+ ): Record<string, string> {
41
+ if (packages == null) {
42
+ throw new Error(
43
+ "No packages: does app.config.ts put installedPackages(projectRoot) in the config's extra?",
44
+ );
45
+ }
46
+ return Object.fromEntries(
47
+ Object.keys(moduleMap).map((specifier) => {
48
+ const version = packages[packageOf(specifier)];
49
+ if (version === undefined) {
50
+ throw new Error(
51
+ `The module map offers ${JSON.stringify(specifier)}, but ${packageOf(specifier)} isn't in package.json's dependencies.`,
52
+ );
53
+ }
54
+ return [specifier, version];
55
+ }),
56
+ );
57
+ }
package/src/node.ts ADDED
@@ -0,0 +1,43 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+
4
+ // Where a package is installed for the app at `projectRoot`, as Node finds
5
+ // it: in its node_modules, or the nearest above, as a workspace hoists it.
6
+ function installed(projectRoot: string, name: string): string {
7
+ for (let directory = projectRoot; ; directory = dirname(directory)) {
8
+ const file = join(directory, "node_modules", name, "package.json");
9
+ if (existsSync(file)) return file;
10
+ if (dirname(directory) === directory) {
11
+ throw new Error(`${projectRoot}: ${name} isn't installed`);
12
+ }
13
+ }
14
+ }
15
+
16
+ /**
17
+ * The installed version of each of the app's dependencies, at `projectRoot`:
18
+ * what a build made from it now carries there, for `modulesOf`. Node only: an
19
+ * Expo app calls it from `app.config.ts`, and passes it on in the config.
20
+ *
21
+ * export default ({ config, projectRoot }: ConfigContext) => ({
22
+ * ...config,
23
+ * extra: {
24
+ * ...config.extra,
25
+ * backtick: { packages: installedPackages(projectRoot) },
26
+ * },
27
+ * });
28
+ */
29
+ export function installedPackages(projectRoot: string): Record<string, string> {
30
+ const { dependencies = {} } = JSON.parse(
31
+ readFileSync(join(projectRoot, "package.json"), "utf8"),
32
+ ) as { dependencies?: Record<string, string> };
33
+ return Object.fromEntries(
34
+ Object.keys(dependencies).map((name) => [
35
+ name,
36
+ (
37
+ JSON.parse(readFileSync(installed(projectRoot, name), "utf8")) as {
38
+ version: string;
39
+ }
40
+ ).version,
41
+ ]),
42
+ );
43
+ }
@@ -1,16 +0,0 @@
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/dist/ModuleMap.js DELETED
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=ModuleMap.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ModuleMap.js","sourceRoot":"","sources":["../src/ModuleMap.ts"],"names":[],"mappings":""}
package/src/ModuleMap.ts DELETED
@@ -1,16 +0,0 @@
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>>;