@backtickjs/tsc 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jean-François Bisson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # @backtickjs/tsc
2
+
3
+ `backtick-tsc` is `tsc` that understands `cs` client scripts. Plain `tsc` sees a script as a template string typed `Client<unknown>`; `backtick-tsc` type-checks the code inside it and every splice (`$name`) crossing into it from the server, and reports Backtick's own compiler errors alongside TypeScript's. Use it to type-check a project in CI or from a `typecheck` script, and to write declarations for a package that exports scripts.
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ npm install --save-dev @backtickjs/tsc typescript
9
+ ```
10
+
11
+ Requires TypeScript 6.
12
+
13
+ ## Usage
14
+
15
+ Type-check, as you would with `tsc --noEmit`:
16
+
17
+ ```json
18
+ {
19
+ "scripts": {
20
+ "typecheck": "backtick-tsc --noEmit"
21
+ }
22
+ }
23
+ ```
24
+
25
+ It takes `tsc`'s arguments, for example a specific config:
26
+
27
+ ```sh
28
+ backtick-tsc -p tsconfig.build.json --noEmit
29
+ ```
30
+
31
+ Write declarations for a library:
32
+
33
+ ```sh
34
+ backtick-tsc --emitDeclarationOnly
35
+ ```
36
+
37
+ A script's declaration carries its real type, where plain `tsc` would write `Client<unknown>`, and its parameter names read as you wrote them.
38
+
39
+ ## Notes
40
+
41
+ - It writes declarations only. Run it with `--noEmit` or `--emitDeclarationOnly`; any other emit is refused with an error, because the JavaScript it would write is the code the type checker reads, not code that runs. To build JavaScript, use a loader ([`@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)) or `tspc` with [`@backtickjs/tspatch-plugin`](https://github.com/backtickjs/backtick/tree/main/packages/tspatch-plugin).
42
+ - Editors show the same diagnostics through [`@backtickjs/typescript-plugin`](https://github.com/backtickjs/backtick/tree/main/packages/typescript-plugin) and [Backtick for VS Code](https://marketplace.visualstudio.com/items?itemName=backtickjs.backtick-vscode).
43
+
44
+ ## Related
45
+
46
+ - [`@backtickjs/tspatch-plugin`](https://github.com/backtickjs/backtick/tree/main/packages/tspatch-plugin): builds JavaScript with `tspc`
47
+ - [`@backtickjs/node-plugin`](https://github.com/backtickjs/backtick/tree/main/packages/node-plugin) and [`@backtickjs/bun-plugin`](https://github.com/backtickjs/backtick/tree/main/packages/bun-plugin): compile at load time
48
+ - [Documentation](https://backtickjs.com/docs)
49
+
50
+ ## License
51
+
52
+ MIT
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { run } from "../dist/index.js";
4
+
5
+ run();
@@ -0,0 +1 @@
1
+ export declare function run(tscPath?: string): any;
package/dist/index.js ADDED
@@ -0,0 +1,107 @@
1
+ import { createRequire } from "node:module";
2
+ import { unmangle } from "@backtickjs/compiler";
3
+ import { getBacktickLanguagePlugin } from "@backtickjs/language-plugin";
4
+ import { getBacktickDiagnostics, unmangleDiagnostic, } from "@backtickjs/language-service";
5
+ import { fillSourceFileText } from "@volar/typescript/lib/node/transform.js";
6
+ import { runTsc } from "@volar/typescript/lib/quickstart/runTsc.js";
7
+ const require = createRequire(import.meta.url);
8
+ export function run(tscPath = require.resolve("typescript/lib/tsc.js")) {
9
+ // Use the standalone TypeScript API, not the `ts` handed to the callback: that
10
+ // one is `runTsc`'s internal tsc.js CLI bundle, whose namespace members are
11
+ // lazy `eval`-based getters that throw when read outside their module scope
12
+ // (Backtick's `parseFile` reads `ts.ScriptTarget`).
13
+ const ts = require("typescript");
14
+ patchDecorateProgram(ts);
15
+ return runTsc(tscPath,
16
+ // No extra file extensions: Backtick virtualizes standard .ts/.tsx/.js/.jsx
17
+ // in place, matching the TS server plugins (which also pass []).
18
+ [], () => ({
19
+ languagePlugins: [getBacktickLanguagePlugin(ts, (fileName) => fileName)],
20
+ }));
21
+ }
22
+ // What this tool is for beyond checking: a package that exports a client script
23
+ // gets a declaration carrying the script's real type. Plain `tsc` types
24
+ // `cs`...`` by the tag's own signature, `Client<unknown>`, because the
25
+ // precise type lives in the virtual code — which is what this program checks,
26
+ // and so what this program can write down.
27
+ //
28
+ // The names in it are the compiler's, though: `(__cs_n: number)` where a reader
29
+ // expects `(n: number)`. An `afterDeclarations` transformer renames them where
30
+ // the emitter prints them, on identifiers alone, so nothing inside a string
31
+ // literal is touched. Not at the file system: `sys.writeFile` writes through
32
+ // `openSync`, and the CLI bundle's `sys` is not this module's anyway.
33
+ function unmangleDeclarations(ts, program) {
34
+ const emit = program.emit.bind(program);
35
+ program.emit = (targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers) => emit(targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, {
36
+ ...customTransformers,
37
+ afterDeclarations: [
38
+ ...(customTransformers?.afterDeclarations ?? []),
39
+ // `context.factory` is the emitter's own, which is what makes this safe
40
+ // to hand over from here: `visitEachChild` builds with the factory it
41
+ // is given rather than with this module's TypeScript.
42
+ (context) => (node) => {
43
+ const visit = (child) => {
44
+ if (ts.isIdentifier(child)) {
45
+ // Read through `escapedText` rather than `text`: an identifier
46
+ // the declaration emitter built carries the escaped form, and
47
+ // one starting with `__` is stored with a `_` in front of it.
48
+ const written = ts.unescapeLeadingUnderscores(child.escapedText);
49
+ const plain = unmangle(written);
50
+ return plain === written
51
+ ? child
52
+ : context.factory.createIdentifier(plain);
53
+ }
54
+ return ts.visitEachChild(child, visit, context);
55
+ };
56
+ return ts.visitNode(node, visit);
57
+ },
58
+ ],
59
+ });
60
+ }
61
+ // The JavaScript this would write is the virtual code — `cs.lift(…)`,
62
+ // which throws where it is called. Refused rather than emitted, because the
63
+ // output would look like a build that worked.
64
+ function refuseJavaScriptEmit(options) {
65
+ if (options.noEmit === true || options.emitDeclarationOnly === true) {
66
+ return;
67
+ }
68
+ throw new Error("`backtick-tsc` writes declarations, not JavaScript — what it would emit " +
69
+ "is the virtual code the typechecker reads. Run it with " +
70
+ "`--emitDeclarationOnly` or `--noEmit`, and leave the JavaScript to " +
71
+ "`tspc` or the bundler plugin.");
72
+ }
73
+ // Surfaces Backtick's own compiler diagnostics through `tsc` by wrapping the
74
+ // program's `getSemanticDiagnostics`. This has to patch Volar's `decorateProgram`
75
+ // module in place: `runTsc` proxies TypeScript internally and calls the module's
76
+ // export by property access at program-creation time, so reassigning it here is
77
+ // picked up.
78
+ function patchDecorateProgram(ts) {
79
+ const decorateProgramModule = require("@volar/typescript/lib/node/decorateProgram.js");
80
+ const volarDecorateProgram = decorateProgramModule.decorateProgram;
81
+ decorateProgramModule.decorateProgram = (language, program) => {
82
+ volarDecorateProgram(language, program);
83
+ refuseJavaScriptEmit(program.getCompilerOptions());
84
+ unmangleDeclarations(ts, program);
85
+ const getSemanticDiagnostics = program.getSemanticDiagnostics.bind(program);
86
+ program.getSemanticDiagnostics = (sourceFile, cancellationToken) => {
87
+ const files = sourceFile ? [sourceFile] : program.getSourceFiles();
88
+ return [
89
+ ...getSemanticDiagnostics(sourceFile, cancellationToken).map(unmangleDiagnostic),
90
+ ...files.flatMap((file) => {
91
+ const diagnostics = getBacktickDiagnostics(language, file, file.fileName);
92
+ if (diagnostics.length > 0) {
93
+ // The program's `file.text` is the compiled embedded code, but the
94
+ // diagnostics carry source-coordinate ranges. Reuse Volar's
95
+ // `fillSourceFileText` to swap the already-parsed file's leading
96
+ // text back to the source snapshot in place — the same fix-up Volar
97
+ // applies to its own transformed diagnostics, and idempotent, so it
98
+ // stays a no-op when the file was already filled.
99
+ fillSourceFileText(language, file);
100
+ }
101
+ return diagnostics;
102
+ }),
103
+ ];
104
+ };
105
+ };
106
+ }
107
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@backtickjs/tsc",
3
+ "version": "0.1.0",
4
+ "description": "backtick-tsc: tsc that type-checks Backtick client scripts and every splice crossing into them",
5
+ "keywords": [
6
+ "backtick",
7
+ "react-native",
8
+ "server-driven-ui",
9
+ "server-components",
10
+ "tsc",
11
+ "typescript",
12
+ "typecheck",
13
+ "cli"
14
+ ],
15
+ "homepage": "https://backtickjs.com",
16
+ "bugs": {
17
+ "url": "https://github.com/backtickjs/backtick/issues"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/backtickjs/backtick.git",
22
+ "directory": "packages/tsc"
23
+ },
24
+ "license": "MIT",
25
+ "author": "Jean-François Bisson <jf@backtickjs.com>",
26
+ "type": "module",
27
+ "bin": {
28
+ "backtick-tsc": "./bin/backtick-tsc.js"
29
+ },
30
+ "files": [
31
+ "bin",
32
+ "dist",
33
+ "!dist/._cache",
34
+ "src"
35
+ ],
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "dependencies": {
40
+ "@volar/typescript": "^2.4.28",
41
+ "@backtickjs/compiler": "0.1.0",
42
+ "@backtickjs/language-plugin": "0.1.0",
43
+ "@backtickjs/language-service": "0.1.0"
44
+ },
45
+ "peerDependencies": {
46
+ "typescript": "^6.0.3"
47
+ },
48
+ "devDependencies": {
49
+ "prettier": "^3.6.2",
50
+ "typescript": "^6.0.3"
51
+ },
52
+ "scripts": {
53
+ "build": "tsc -b",
54
+ "dev": "tsc -b --watch",
55
+ "format": "prettier --write --ignore-unknown .",
56
+ "format:check": "prettier --check --ignore-unknown .",
57
+ "clean": "rm -rf node_modules dist .turbo"
58
+ }
59
+ }
package/src/index.ts ADDED
@@ -0,0 +1,144 @@
1
+ import { createRequire } from "node:module";
2
+ import { unmangle } from "@backtickjs/compiler";
3
+ import { getBacktickLanguagePlugin } from "@backtickjs/language-plugin";
4
+ import {
5
+ getBacktickDiagnostics,
6
+ unmangleDiagnostic,
7
+ } from "@backtickjs/language-service";
8
+ import { fillSourceFileText } from "@volar/typescript/lib/node/transform.js";
9
+ import { runTsc } from "@volar/typescript/lib/quickstart/runTsc.js";
10
+ import type ts from "typescript";
11
+
12
+ const require = createRequire(import.meta.url);
13
+
14
+ export function run(tscPath = require.resolve("typescript/lib/tsc.js")) {
15
+ // Use the standalone TypeScript API, not the `ts` handed to the callback: that
16
+ // one is `runTsc`'s internal tsc.js CLI bundle, whose namespace members are
17
+ // lazy `eval`-based getters that throw when read outside their module scope
18
+ // (Backtick's `parseFile` reads `ts.ScriptTarget`).
19
+ const ts = require("typescript") as typeof import("typescript");
20
+
21
+ patchDecorateProgram(ts);
22
+
23
+ return runTsc(
24
+ tscPath,
25
+ // No extra file extensions: Backtick virtualizes standard .ts/.tsx/.js/.jsx
26
+ // in place, matching the TS server plugins (which also pass []).
27
+ [],
28
+ () => ({
29
+ languagePlugins: [getBacktickLanguagePlugin(ts, (fileName) => fileName)],
30
+ }),
31
+ );
32
+ }
33
+
34
+ // What this tool is for beyond checking: a package that exports a client script
35
+ // gets a declaration carrying the script's real type. Plain `tsc` types
36
+ // `cs`...`` by the tag's own signature, `Client<unknown>`, because the
37
+ // precise type lives in the virtual code — which is what this program checks,
38
+ // and so what this program can write down.
39
+ //
40
+ // The names in it are the compiler's, though: `(__cs_n: number)` where a reader
41
+ // expects `(n: number)`. An `afterDeclarations` transformer renames them where
42
+ // the emitter prints them, on identifiers alone, so nothing inside a string
43
+ // literal is touched. Not at the file system: `sys.writeFile` writes through
44
+ // `openSync`, and the CLI bundle's `sys` is not this module's anyway.
45
+ function unmangleDeclarations(
46
+ ts: typeof import("typescript"),
47
+ program: ts.Program,
48
+ ): void {
49
+ const emit = program.emit.bind(program);
50
+ program.emit = (
51
+ targetSourceFile,
52
+ writeFile,
53
+ cancellationToken,
54
+ emitOnlyDtsFiles,
55
+ customTransformers,
56
+ ) =>
57
+ emit(targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, {
58
+ ...customTransformers,
59
+ afterDeclarations: [
60
+ ...(customTransformers?.afterDeclarations ?? []),
61
+ // `context.factory` is the emitter's own, which is what makes this safe
62
+ // to hand over from here: `visitEachChild` builds with the factory it
63
+ // is given rather than with this module's TypeScript.
64
+ (context) => (node) => {
65
+ const visit = (child: ts.Node): ts.Node => {
66
+ if (ts.isIdentifier(child)) {
67
+ // Read through `escapedText` rather than `text`: an identifier
68
+ // the declaration emitter built carries the escaped form, and
69
+ // one starting with `__` is stored with a `_` in front of it.
70
+ const written = ts.unescapeLeadingUnderscores(child.escapedText);
71
+ const plain = unmangle(written);
72
+ return plain === written
73
+ ? child
74
+ : context.factory.createIdentifier(plain);
75
+ }
76
+ return ts.visitEachChild(child, visit, context);
77
+ };
78
+ return ts.visitNode(node, visit) as typeof node;
79
+ },
80
+ ],
81
+ });
82
+ }
83
+
84
+ // The JavaScript this would write is the virtual code — `cs.lift(…)`,
85
+ // which throws where it is called. Refused rather than emitted, because the
86
+ // output would look like a build that worked.
87
+ function refuseJavaScriptEmit(options: {
88
+ noEmit?: boolean;
89
+ emitDeclarationOnly?: boolean;
90
+ }) {
91
+ if (options.noEmit === true || options.emitDeclarationOnly === true) {
92
+ return;
93
+ }
94
+ throw new Error(
95
+ "`backtick-tsc` writes declarations, not JavaScript — what it would emit " +
96
+ "is the virtual code the typechecker reads. Run it with " +
97
+ "`--emitDeclarationOnly` or `--noEmit`, and leave the JavaScript to " +
98
+ "`tspc` or the bundler plugin.",
99
+ );
100
+ }
101
+
102
+ // Surfaces Backtick's own compiler diagnostics through `tsc` by wrapping the
103
+ // program's `getSemanticDiagnostics`. This has to patch Volar's `decorateProgram`
104
+ // module in place: `runTsc` proxies TypeScript internally and calls the module's
105
+ // export by property access at program-creation time, so reassigning it here is
106
+ // picked up.
107
+ function patchDecorateProgram(ts: typeof import("typescript")) {
108
+ const decorateProgramModule =
109
+ require("@volar/typescript/lib/node/decorateProgram.js") as typeof import("@volar/typescript/lib/node/decorateProgram.js");
110
+ const volarDecorateProgram = decorateProgramModule.decorateProgram;
111
+
112
+ decorateProgramModule.decorateProgram = (language, program) => {
113
+ volarDecorateProgram(language, program);
114
+ refuseJavaScriptEmit(program.getCompilerOptions());
115
+ unmangleDeclarations(ts, program);
116
+
117
+ const getSemanticDiagnostics = program.getSemanticDiagnostics.bind(program);
118
+ program.getSemanticDiagnostics = (sourceFile, cancellationToken) => {
119
+ const files = sourceFile ? [sourceFile] : program.getSourceFiles();
120
+ return [
121
+ ...getSemanticDiagnostics(sourceFile, cancellationToken).map(
122
+ unmangleDiagnostic,
123
+ ),
124
+ ...files.flatMap((file) => {
125
+ const diagnostics = getBacktickDiagnostics(
126
+ language,
127
+ file,
128
+ file.fileName,
129
+ );
130
+ if (diagnostics.length > 0) {
131
+ // The program's `file.text` is the compiled embedded code, but the
132
+ // diagnostics carry source-coordinate ranges. Reuse Volar's
133
+ // `fillSourceFileText` to swap the already-parsed file's leading
134
+ // text back to the source snapshot in place — the same fix-up Volar
135
+ // applies to its own transformed diagnostics, and idempotent, so it
136
+ // stays a no-op when the file was already filled.
137
+ fillSourceFileText(language, file);
138
+ }
139
+ return diagnostics;
140
+ }),
141
+ ];
142
+ };
143
+ };
144
+ }