@backtickjs/bun-plugin 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,69 @@
1
+ # @backtickjs/bun-plugin
2
+
3
+ Runs a Backtick server on Bun without a build step. It's a Bun plugin, preloaded, that compiles your server's TypeScript and JSX files as they load, including their `cs` client scripts and the framework compile steps your project names. Use it in development and in production when Bun is your runtime; on Node, use [`@backtickjs/node-plugin`](https://github.com/backtickjs/backtick/tree/main/packages/node-plugin) instead.
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ bun add @backtickjs/bun-plugin typescript
9
+ ```
10
+
11
+ It's a runtime dependency: the server needs it whenever it starts. Requires TypeScript 6.
12
+
13
+ ## Usage
14
+
15
+ Preload it from `bunfig.toml`:
16
+
17
+ ```toml
18
+ preload = ["@backtickjs/bun-plugin"]
19
+ ```
20
+
21
+ or from the command line:
22
+
23
+ ```sh
24
+ bun --preload @backtickjs/bun-plugin server/index.tsx
25
+ ```
26
+
27
+ Run it from the project root: the plugin reads `package.json` from the current directory. In development, add `--watch` to restart the server when a file changes:
28
+
29
+ ```sh
30
+ bun --watch --preload @backtickjs/bun-plugin server/index.tsx
31
+ ```
32
+
33
+ What it does:
34
+
35
+ - Compiles every `.ts` and `.tsx` file outside `node_modules`, each with the `tsconfig.json` nearest to it, so a server and an app beside it can each have their own. (Bun alone reads only the root `tsconfig.json`.)
36
+ - Compiles each `cs` script in those files, with the framework compile steps named in `package.json` (below). A script the compiler refuses throws its error, and the file doesn't load.
37
+
38
+ ## Configuration
39
+
40
+ Name your framework's compile steps in the project's `package.json`:
41
+
42
+ ```json
43
+ {
44
+ "backtick": {
45
+ "plugins": ["@backtickjs/react/plugin"]
46
+ }
47
+ }
48
+ ```
49
+
50
+ Use `@backtickjs/solid-js/plugin` for Solid. The steps are loaded once, at startup, with the project's own `require`.
51
+
52
+ Compiler options come from the nearest `tsconfig.json`, following `extends`. The plugin always emits ES modules (`ESNext`) with an inline source map, and ignores `outDir`, `rootDir` and other output-location options.
53
+
54
+ ## Notes
55
+
56
+ - The plugin compiles; it doesn't type-check. Run [`backtick-tsc`](https://github.com/backtickjs/backtick/tree/main/packages/tsc) for that.
57
+ - For stack traces that point at your files, the plugin stamps a `// @bun` pragma on its output so Bun uses its inline source map. This relies on undocumented Bun behavior and could break on a Bun upgrade.
58
+ - With that pragma, Bun reads the output as Latin-1, so the plugin escapes every non-ASCII character as `\uXXXX`. Strings and identifiers keep their meaning, but stack-trace columns after non-ASCII text on the same line may be off; line numbers stay right.
59
+ - Files under `node_modules` are handed back to Bun uncompiled.
60
+
61
+ ## Related
62
+
63
+ - [`@backtickjs/node-plugin`](https://github.com/backtickjs/backtick/tree/main/packages/node-plugin): the same for Node
64
+ - [`@backtickjs/tsc`](https://github.com/backtickjs/backtick/tree/main/packages/tsc): type-checks scripts and their splices
65
+ - [Documentation](https://backtickjs.com/docs)
66
+
67
+ ## License
68
+
69
+ MIT
@@ -0,0 +1 @@
1
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,86 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { createRequire } from "node:module";
3
+ import path from "node:path";
4
+ import { compileModule, pluginsFrom } from "@backtickjs/compiler";
5
+ import { plugin } from "bun";
6
+ import ts from "typescript";
7
+ const packageJson = path.join(process.cwd(), "package.json");
8
+ const plugins = pluginsFrom(readFileSync(packageJson, "utf8"), createRequire(packageJson));
9
+ /**
10
+ * Use it from a `bunfig.toml`:
11
+ *
12
+ * ```toml
13
+ * preload = ["@backtickjs/bun-plugin"]
14
+ * ```
15
+ *
16
+ * or from the command line:
17
+ *
18
+ * ```sh
19
+ * bun --preload @backtickjs/bun-plugin ./src/index.ts
20
+ * ```
21
+ *
22
+ * The framework's compile steps are the project's, named in its
23
+ * `package.json`:
24
+ *
25
+ * ```json
26
+ * "backtick": { "plugins": ["@backtickjs/solid-js/plugin"] }
27
+ * ```
28
+ */
29
+ plugin({
30
+ name: "backtick",
31
+ setup(build) {
32
+ build.onLoad({ filter: /\.tsx?$/ }, (args) => {
33
+ const source = readFileSync(args.path, "utf8");
34
+ // What the project installed is left to Bun. Returning `undefined` here
35
+ // throws on Bun >= 1.3 ("onLoad() expects an object returned"), so hand
36
+ // back the original source with a plain TS/TSX loader instead.
37
+ if (args.path.includes("node_modules")) {
38
+ return {
39
+ contents: source,
40
+ loader: args.path.endsWith(".tsx") ? "tsx" : "ts",
41
+ };
42
+ }
43
+ // The project's own files are all compiled here, not only those with a
44
+ // script: Bun reads only the root `tsconfig.json`, and a server beside an
45
+ // app has its own, nearer one.
46
+ const fileName = path.relative(process.cwd(), args.path).split(path.sep).join("/") ||
47
+ args.path;
48
+ const code = compileModule(ts, fileName, source, { plugins });
49
+ return {
50
+ contents: ascii(stampBun(code)),
51
+ loader: args.path.endsWith(".tsx") ? "jsx" : "js",
52
+ };
53
+ });
54
+ },
55
+ });
56
+ // Code with every character past ASCII escaped, `\uXXXX`: Bun reads a file
57
+ // stamped `// @bun` (below) as Latin-1, as its own transpiler writes them, so
58
+ // "🏡" would arrive as "ð\u009f\u008f¡". Escaped, a string, identifier or
59
+ // regular expression means what it did. (JSX text doesn't read escapes, but
60
+ // what reaches here is JSX compiled to calls unless a project preserves it.)
61
+ // The source map isn't shifted to match, so columns after an escape on its
62
+ // line are off; lines stay right, which is worth it for text that reads right.
63
+ function ascii(code) {
64
+ return code.replace(/[^\x00-\x7f]/g, (unit) => `\\u${unit.charCodeAt(0).toString(16).padStart(4, "0")}`);
65
+ }
66
+ // HACK: stamp a `// @bun` pragma at the top of the output to trick Bun into
67
+ // using our inline source map.
68
+ //
69
+ // This relies on undocumented Bun internals and could break on any Bun upgrade.
70
+ // The proper fix would be a real Bun plugin API for handing back a source map.
71
+ //
72
+ // Bun reads the pragma only on the very first line, so it's written into the
73
+ // text rather than onto the first statement, where a leading comment would push
74
+ // it down. The map gains an empty first line to match.
75
+ const MAP = "//# sourceMappingURL=data:application/json;base64,";
76
+ function stampBun(code) {
77
+ const at = code.lastIndexOf(MAP);
78
+ if (at === -1) {
79
+ return `// @bun\n${code}`;
80
+ }
81
+ const map = JSON.parse(Buffer.from(code.slice(at + MAP.length).trim(), "base64").toString("utf8"));
82
+ map.mappings = `;${map.mappings}`;
83
+ const encoded = Buffer.from(JSON.stringify(map)).toString("base64");
84
+ return `// @bun\n${code.slice(0, at)}${MAP}${encoded}\n`;
85
+ }
86
+ //# 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,61 @@
1
+ {
2
+ "name": "@backtickjs/bun-plugin",
3
+ "version": "0.1.0",
4
+ "description": "Runs a Backtick server on Bun without a build step, compiling its files and client scripts as they load",
5
+ "keywords": [
6
+ "backtick",
7
+ "react-native",
8
+ "server-driven-ui",
9
+ "server-components",
10
+ "bun",
11
+ "plugin",
12
+ "loader"
13
+ ],
14
+ "homepage": "https://backtickjs.com",
15
+ "bugs": {
16
+ "url": "https://github.com/backtickjs/backtick/issues"
17
+ },
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/backtickjs/backtick.git",
21
+ "directory": "packages/bun-plugin"
22
+ },
23
+ "license": "MIT",
24
+ "author": "Jean-François Bisson <jf@backtickjs.com>",
25
+ "type": "module",
26
+ "main": "./dist/index.js",
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/index.d.ts",
30
+ "default": "./dist/index.js"
31
+ }
32
+ },
33
+ "files": [
34
+ "dist",
35
+ "!dist/._cache",
36
+ "src"
37
+ ],
38
+ "publishConfig": {
39
+ "access": "public"
40
+ },
41
+ "dependencies": {
42
+ "@backtickjs/compiler": "0.1.0"
43
+ },
44
+ "peerDependencies": {
45
+ "typescript": "^6.0.3"
46
+ },
47
+ "devDependencies": {
48
+ "@types/bun": "^1.3.14",
49
+ "prettier": "^3.6.2",
50
+ "typescript": "^6.0.3",
51
+ "@backtickjs/core": "0.1.0"
52
+ },
53
+ "scripts": {
54
+ "build": "tsc -b",
55
+ "dev": "tsc -b --watch",
56
+ "test": "bun test",
57
+ "format": "prettier --write --ignore-unknown .",
58
+ "format:check": "prettier --check --ignore-unknown .",
59
+ "clean": "rm -rf node_modules dist .turbo"
60
+ }
61
+ }
package/src/index.ts ADDED
@@ -0,0 +1,102 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { createRequire } from "node:module";
3
+ import path from "node:path";
4
+ import { compileModule, pluginsFrom } from "@backtickjs/compiler";
5
+ import { plugin } from "bun";
6
+ import ts from "typescript";
7
+
8
+ const packageJson = path.join(process.cwd(), "package.json");
9
+ const plugins = pluginsFrom(
10
+ readFileSync(packageJson, "utf8"),
11
+ createRequire(packageJson),
12
+ );
13
+
14
+ /**
15
+ * Use it from a `bunfig.toml`:
16
+ *
17
+ * ```toml
18
+ * preload = ["@backtickjs/bun-plugin"]
19
+ * ```
20
+ *
21
+ * or from the command line:
22
+ *
23
+ * ```sh
24
+ * bun --preload @backtickjs/bun-plugin ./src/index.ts
25
+ * ```
26
+ *
27
+ * The framework's compile steps are the project's, named in its
28
+ * `package.json`:
29
+ *
30
+ * ```json
31
+ * "backtick": { "plugins": ["@backtickjs/solid-js/plugin"] }
32
+ * ```
33
+ */
34
+ plugin({
35
+ name: "backtick",
36
+ setup(build) {
37
+ build.onLoad({ filter: /\.tsx?$/ }, (args) => {
38
+ const source = readFileSync(args.path, "utf8");
39
+
40
+ // What the project installed is left to Bun. Returning `undefined` here
41
+ // throws on Bun >= 1.3 ("onLoad() expects an object returned"), so hand
42
+ // back the original source with a plain TS/TSX loader instead.
43
+ if (args.path.includes("node_modules")) {
44
+ return {
45
+ contents: source,
46
+ loader: args.path.endsWith(".tsx") ? "tsx" : "ts",
47
+ };
48
+ }
49
+
50
+ // The project's own files are all compiled here, not only those with a
51
+ // script: Bun reads only the root `tsconfig.json`, and a server beside an
52
+ // app has its own, nearer one.
53
+ const fileName =
54
+ path.relative(process.cwd(), args.path).split(path.sep).join("/") ||
55
+ args.path;
56
+ const code = compileModule(ts, fileName, source, { plugins });
57
+
58
+ return {
59
+ contents: ascii(stampBun(code)),
60
+ loader: args.path.endsWith(".tsx") ? "jsx" : "js",
61
+ };
62
+ });
63
+ },
64
+ });
65
+
66
+ // Code with every character past ASCII escaped, `\uXXXX`: Bun reads a file
67
+ // stamped `// @bun` (below) as Latin-1, as its own transpiler writes them, so
68
+ // "🏡" would arrive as "ð\u009f\u008f¡". Escaped, a string, identifier or
69
+ // regular expression means what it did. (JSX text doesn't read escapes, but
70
+ // what reaches here is JSX compiled to calls unless a project preserves it.)
71
+ // The source map isn't shifted to match, so columns after an escape on its
72
+ // line are off; lines stay right, which is worth it for text that reads right.
73
+ function ascii(code: string): string {
74
+ return code.replace(
75
+ /[^\x00-\x7f]/g,
76
+ (unit) => `\\u${unit.charCodeAt(0).toString(16).padStart(4, "0")}`,
77
+ );
78
+ }
79
+
80
+ // HACK: stamp a `// @bun` pragma at the top of the output to trick Bun into
81
+ // using our inline source map.
82
+ //
83
+ // This relies on undocumented Bun internals and could break on any Bun upgrade.
84
+ // The proper fix would be a real Bun plugin API for handing back a source map.
85
+ //
86
+ // Bun reads the pragma only on the very first line, so it's written into the
87
+ // text rather than onto the first statement, where a leading comment would push
88
+ // it down. The map gains an empty first line to match.
89
+ const MAP = "//# sourceMappingURL=data:application/json;base64,";
90
+
91
+ function stampBun(code: string): string {
92
+ const at = code.lastIndexOf(MAP);
93
+ if (at === -1) {
94
+ return `// @bun\n${code}`;
95
+ }
96
+ const map = JSON.parse(
97
+ Buffer.from(code.slice(at + MAP.length).trim(), "base64").toString("utf8"),
98
+ ) as { mappings: string };
99
+ map.mappings = `;${map.mappings}`;
100
+ const encoded = Buffer.from(JSON.stringify(map)).toString("base64");
101
+ return `// @bun\n${code.slice(0, at)}${MAP}${encoded}\n`;
102
+ }