@ttsc/unplugin 0.20.1 → 0.21.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 +3 -1
- package/lib/api.js +3 -0
- package/lib/api.js.map +1 -1
- package/lib/api.mjs +2 -2
- package/lib/bun.d.ts +41 -15
- package/lib/bun.js +88 -32
- package/lib/bun.js.map +1 -1
- package/lib/bun.mjs +88 -32
- package/lib/bun.mjs.map +1 -1
- package/lib/core/index.d.ts +10 -2
- package/lib/core/index.js +12 -4
- package/lib/core/index.js.map +1 -1
- package/lib/core/index.mjs +11 -6
- package/lib/core/index.mjs.map +1 -1
- package/lib/core/transform.d.ts +40 -16
- package/lib/core/transform.js +154 -80
- package/lib/core/transform.js.map +1 -1
- package/lib/core/transform.mjs +153 -81
- package/lib/core/transform.mjs.map +1 -1
- package/lib/turbopack.js +3 -3
- package/lib/turbopack.mjs +3 -3
- package/package.json +3 -3
- package/src/bun.ts +132 -52
- package/src/core/index.ts +13 -5
- package/src/core/transform.ts +181 -96
- package/src/turbopack.ts +3 -3
package/src/bun.ts
CHANGED
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
|
-
import type { UnpluginContextMeta } from "unplugin";
|
|
3
2
|
|
|
4
|
-
import {
|
|
5
|
-
|
|
3
|
+
import {
|
|
4
|
+
beginTtscTransformBuild,
|
|
5
|
+
createTtscTransformCache,
|
|
6
|
+
isTransformTarget,
|
|
7
|
+
resolveOptions,
|
|
8
|
+
transformTtsc,
|
|
9
|
+
} from "./core/index";
|
|
6
10
|
import type { TtscUnpluginOptions } from "./core/options";
|
|
7
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Bun normally reports filesystem source paths, while plugin-created virtual
|
|
14
|
+
* ids may contain a NUL sentinel. A virtual id must stay with the plugin that
|
|
15
|
+
* created it: claiming it here and trying to read it from disk prevents that
|
|
16
|
+
* plugin's later loader from running.
|
|
17
|
+
*/
|
|
18
|
+
const bunSourceFilePattern = /^[^\x00]*\.[cm]?tsx?$/;
|
|
19
|
+
|
|
8
20
|
/**
|
|
9
21
|
* Minimal subset of the Bun plugin API consumed by this adapter.
|
|
10
22
|
*
|
|
@@ -38,7 +50,7 @@ export type TtscBunOptions =
|
|
|
38
50
|
| (() => TtscUnpluginOptions | undefined);
|
|
39
51
|
|
|
40
52
|
/**
|
|
41
|
-
* Transform
|
|
53
|
+
* Transform hooks handed to the shared transform under Bun.
|
|
42
54
|
*
|
|
43
55
|
* The shared transform calls `addWatchFile` once per plugin-reported dependency
|
|
44
56
|
* so type-only inputs can enter a bundler's watch graph. Bun's bundler and
|
|
@@ -49,7 +61,7 @@ export type TtscBunOptions =
|
|
|
49
61
|
* `this.addWatchFile` `undefined`, so any plugin reporting dependencies threw
|
|
50
62
|
* `TypeError: this.addWatchFile is not a function`.
|
|
51
63
|
*/
|
|
52
|
-
const
|
|
64
|
+
const bunTransformHooks = {
|
|
53
65
|
addWatchFile(): void {},
|
|
54
66
|
};
|
|
55
67
|
|
|
@@ -63,91 +75,159 @@ function resolveBunOptions(
|
|
|
63
75
|
/**
|
|
64
76
|
* Minimal subset of the Bun `BuildConfig` plugin build object.
|
|
65
77
|
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
78
|
+
* `onLoad` drives the source transform. Bun's bundler also exposes `onStart`,
|
|
79
|
+
* which is used when available to forward the shared plugin's build lifecycle
|
|
80
|
+
* and clear its per-build cache. The runtime plugin API omits that hook, so
|
|
81
|
+
* plugin setup itself starts its one process-scoped module-loading session.
|
|
68
82
|
*/
|
|
69
83
|
export interface BunLikeBuild {
|
|
84
|
+
/**
|
|
85
|
+
* Build configuration exposed unchanged by Bun's bundler plugin builder.
|
|
86
|
+
*
|
|
87
|
+
* Runtime plugin builders do not supply `files`. Bun's bundler accepts an
|
|
88
|
+
* in-memory file map whose values deliberately remain `unknown` here because
|
|
89
|
+
* this adapter only needs to preserve ownership, not consume their contents.
|
|
90
|
+
*/
|
|
91
|
+
config?: {
|
|
92
|
+
files?: Readonly<Record<string, unknown>>;
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* Register a callback for the start of a bundler build.
|
|
96
|
+
*
|
|
97
|
+
* Optional because `Bun.plugin()` runtime builders do not expose this hook.
|
|
98
|
+
*/
|
|
99
|
+
onStart?(callback: () => void | Promise<void>): void;
|
|
70
100
|
/**
|
|
71
101
|
* Register a loader callback for files matching `filter`.
|
|
72
102
|
*
|
|
73
|
-
* The callback receives the
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
103
|
+
* The callback receives the file path and must return the transformed file
|
|
104
|
+
* contents plus the `loader` Bun should apply next. Configured in-memory
|
|
105
|
+
* files retain relative key spellings; ordinary disk files are normally
|
|
106
|
+
* absolute. The `loader` matters most for the runtime path (`Bun.plugin`),
|
|
107
|
+
* where Bun must be told the returned contents are still TypeScript so it
|
|
108
|
+
* keeps transpiling them before execution.
|
|
78
109
|
*/
|
|
79
110
|
onLoad(
|
|
80
111
|
options: { filter: RegExp },
|
|
81
112
|
loader: (args: {
|
|
82
113
|
path: string;
|
|
83
|
-
}) => Promise<{ contents: string; loader: BunLoader }>,
|
|
114
|
+
}) => Promise<{ contents: string; loader: BunLoader } | undefined>,
|
|
84
115
|
): void;
|
|
85
116
|
}
|
|
86
117
|
|
|
87
118
|
/**
|
|
88
119
|
* Create a ttsc plugin for Bun's bundler AND runtime.
|
|
89
120
|
*
|
|
90
|
-
* Bun does not implement the unplugin protocol, so this adapter
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
121
|
+
* Bun does not implement the unplugin protocol, so this adapter wires the
|
|
122
|
+
* shared ttsc transform core to Bun's `onLoad` hook directly. It reads each
|
|
123
|
+
* included file from disk and forwards the content to the transform. Under
|
|
124
|
+
* `Bun.build`, excluded files and no-op transforms return `undefined` so the
|
|
125
|
+
* next loader retains ownership. Entries supplied through `BuildConfig.files`
|
|
126
|
+
* also stay with Bun's in-memory loader: they are not filesystem project inputs
|
|
127
|
+
* and reading the same path from disk would either fail or silently replace the
|
|
128
|
+
* configured contents. The runtime `Bun.plugin()` API rejects an undefined
|
|
129
|
+
* `onLoad` result, so that path explicitly returns the original source and
|
|
130
|
+
* loader instead.
|
|
95
131
|
*
|
|
96
132
|
* The same object works for `Bun.build({ plugins: [ttsc()] })` (bundler) and
|
|
97
133
|
* for `Bun.plugin(ttsc())` / a `bunfig.toml` preload (runtime) — see
|
|
98
134
|
* `bun-register`. Every result carries an explicit `loader` so Bun keeps
|
|
99
|
-
* transpiling the emitted TypeScript at runtime; `
|
|
100
|
-
* matches TypeScript, so the loader is always `ts`/`tsx`.
|
|
135
|
+
* transpiling the emitted TypeScript at runtime; `bunSourceFilePattern` only
|
|
136
|
+
* matches TypeScript, so the loader is always `ts`/`tsx`. A runtime plugin
|
|
137
|
+
* instance is one immutable load session, like Bun's own module cache; restart
|
|
138
|
+
* the process after changing compiler inputs.
|
|
101
139
|
*/
|
|
102
140
|
export default function bun(options?: TtscBunOptions): BunLikePlugin {
|
|
103
141
|
return {
|
|
104
142
|
name: "ttsc-unplugin",
|
|
105
143
|
setup(build) {
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
144
|
+
// Resolve options lazily on first load. Runtime registration may call
|
|
145
|
+
// register(options) immediately after the import-time default
|
|
146
|
+
// registration; the provider form must observe that last synchronous
|
|
147
|
+
// update without installing a second shadowing loader.
|
|
148
|
+
let resolved: ReturnType<typeof resolveOptions> | undefined;
|
|
149
|
+
const getOptions = () =>
|
|
150
|
+
(resolved ??= resolveOptions(resolveBunOptions(options)));
|
|
151
|
+
const cache = createTtscTransformCache();
|
|
152
|
+
const runtime = build.onStart === undefined;
|
|
153
|
+
const ownsInMemoryFile = createBunInMemoryFileMatcher(build);
|
|
154
|
+
// Bun.plugin() has no onStart callback, but one setup invocation belongs
|
|
155
|
+
// to exactly one runtime process and module-loading session. Mark that
|
|
156
|
+
// session up front so first delivery of every emitted project module is
|
|
157
|
+
// constant-time instead of re-reading the whole project. Bun.build()
|
|
158
|
+
// immediately starts the same initial scope again through onStart and
|
|
159
|
+
// repeats it for subsequent builds.
|
|
160
|
+
beginTtscTransformBuild(cache);
|
|
161
|
+
build.onStart?.(() => beginTtscTransformBuild(cache));
|
|
162
|
+
build.onLoad({ filter: bunSourceFilePattern }, async (args) => {
|
|
163
|
+
if (!runtime && ownsInMemoryFile(args.path)) {
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
if (!isTransformTarget(args.path)) {
|
|
167
|
+
if (!runtime) return undefined;
|
|
168
|
+
return {
|
|
169
|
+
contents: await fs.readFile(args.path, "utf8"),
|
|
170
|
+
loader: bunLoaderFor(args.path),
|
|
171
|
+
};
|
|
172
|
+
}
|
|
118
173
|
const loader = bunLoaderFor(args.path);
|
|
119
174
|
const source = await fs.readFile(args.path, "utf8");
|
|
120
|
-
const result =
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
if (typeof result === "string") {
|
|
130
|
-
return { contents: result, loader };
|
|
131
|
-
}
|
|
132
|
-
if (
|
|
133
|
-
typeof result === "object" &&
|
|
134
|
-
result !== null &&
|
|
135
|
-
"code" in result &&
|
|
136
|
-
typeof result.code === "string"
|
|
137
|
-
) {
|
|
175
|
+
const result = await transformTtsc(
|
|
176
|
+
args.path,
|
|
177
|
+
source,
|
|
178
|
+
getOptions(),
|
|
179
|
+
undefined,
|
|
180
|
+
cache,
|
|
181
|
+
bunTransformHooks,
|
|
182
|
+
);
|
|
183
|
+
if (result !== undefined) {
|
|
138
184
|
return { contents: result.code, loader };
|
|
139
185
|
}
|
|
140
|
-
return { contents: source, loader };
|
|
186
|
+
return runtime ? { contents: source, loader } : undefined;
|
|
141
187
|
});
|
|
142
188
|
},
|
|
143
189
|
};
|
|
144
190
|
}
|
|
145
191
|
|
|
146
192
|
/**
|
|
147
|
-
* Pick the Bun loader for a matched file. `
|
|
193
|
+
* Pick the Bun loader for a matched file. `bunSourceFilePattern` is
|
|
148
194
|
* `/\.[cm]?tsx?$/`, so a trailing `x` (`.tsx`/`.ctsx`/`.mtsx`) is JSX-flavored
|
|
149
195
|
* TypeScript and everything else (`.ts`/`.cts`/`.mts`) is plain TypeScript.
|
|
150
196
|
*/
|
|
151
197
|
function bunLoaderFor(filePath: string): BunLoader {
|
|
152
198
|
return /x$/i.test(filePath) ? "tsx" : "ts";
|
|
153
199
|
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Create a stable ownership matcher for Bun's `BuildConfig.files` map.
|
|
203
|
+
*
|
|
204
|
+
* Bun preserves relative `files` keys in the corresponding `onLoad` path.
|
|
205
|
+
* Preserve relative versus absolute spelling and dot segments exactly. Windows
|
|
206
|
+
* normalizes separators and drive-letter case, but not component case. No path
|
|
207
|
+
* is resolved against cwd, so `process.chdir()` cannot change ownership.
|
|
208
|
+
*/
|
|
209
|
+
function createBunInMemoryFileMatcher(
|
|
210
|
+
build: BunLikeBuild,
|
|
211
|
+
): (file: string) => boolean {
|
|
212
|
+
const files = build.config?.files;
|
|
213
|
+
if (files === undefined) return () => false;
|
|
214
|
+
const identities = new Set(Object.keys(files).map(bunPathIdentityKey));
|
|
215
|
+
return (file) => identities.has(bunPathIdentityKey(file));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Normalize the path forms Bun equates for its in-memory file map.
|
|
220
|
+
*
|
|
221
|
+
* Bun normalizes Windows separators and drive-letter case, but preserves path
|
|
222
|
+
* component case, relative versus absolute spelling, and dot segments. A
|
|
223
|
+
* filesystem identity key is broader and would suppress real disk transforms.
|
|
224
|
+
*/
|
|
225
|
+
function bunPathIdentityKey(file: string): string {
|
|
226
|
+
if (process.platform !== "win32") return file;
|
|
227
|
+
return file
|
|
228
|
+
.replace(/\\/g, "/")
|
|
229
|
+
.replace(
|
|
230
|
+
/^([a-z]):/i,
|
|
231
|
+
(_match, drive: string) => `${drive.toLowerCase()}:`,
|
|
232
|
+
);
|
|
233
|
+
}
|
package/src/core/index.ts
CHANGED
|
@@ -6,11 +6,13 @@ import { createUnplugin } from "unplugin";
|
|
|
6
6
|
import type { TtscUnpluginOptions } from "./options";
|
|
7
7
|
import { resolveOptions } from "./options";
|
|
8
8
|
import {
|
|
9
|
+
beginTtscTransformBuild,
|
|
9
10
|
collectExternalInputHashes,
|
|
10
11
|
collectProjectInputHashes,
|
|
11
12
|
createTtscTransformCache,
|
|
12
13
|
isDeclarationFile,
|
|
13
14
|
isProjectWalkPath,
|
|
15
|
+
resetTtscTransformCache,
|
|
14
16
|
stripQuery,
|
|
15
17
|
transformTtsc,
|
|
16
18
|
} from "./transform";
|
|
@@ -37,9 +39,9 @@ const virtualModulePattern = /\0/;
|
|
|
37
39
|
*
|
|
38
40
|
* The factory resolves raw options once, creates a per-build transform cache,
|
|
39
41
|
* and captures Vite alias configuration via the `vite.configResolved` hook so
|
|
40
|
-
* that path aliases are forwarded to the generated tsconfig overlay.
|
|
41
|
-
*
|
|
42
|
-
*
|
|
42
|
+
* that path aliases are forwarded to the generated tsconfig overlay. Real build
|
|
43
|
+
* lifecycles use a per-build cache; Vite's development server keeps persistent
|
|
44
|
+
* validation because its one `buildStart` spans later HMR edits.
|
|
43
45
|
*/
|
|
44
46
|
const unpluginFactory: UnpluginFactory<
|
|
45
47
|
TtscUnpluginOptions | undefined,
|
|
@@ -81,7 +83,11 @@ const unpluginFactory: UnpluginFactory<
|
|
|
81
83
|
},
|
|
82
84
|
|
|
83
85
|
buildStart() {
|
|
84
|
-
|
|
86
|
+
if (viteCommand === "serve") {
|
|
87
|
+
resetTtscTransformCache(transformCache);
|
|
88
|
+
} else {
|
|
89
|
+
beginTtscTransformBuild(transformCache);
|
|
90
|
+
}
|
|
85
91
|
},
|
|
86
92
|
|
|
87
93
|
transformInclude(id) {
|
|
@@ -142,10 +148,12 @@ export type {
|
|
|
142
148
|
} from "./options";
|
|
143
149
|
export type { TtscTransformHooks } from "./transform";
|
|
144
150
|
export {
|
|
151
|
+
beginTtscTransformBuild,
|
|
145
152
|
collectExternalInputHashes,
|
|
146
153
|
collectProjectInputHashes,
|
|
147
154
|
createTtscTransformCache,
|
|
148
155
|
isProjectWalkPath,
|
|
156
|
+
resetTtscTransformCache,
|
|
149
157
|
resolveOptions,
|
|
150
158
|
transformTtsc,
|
|
151
159
|
unplugin,
|
|
@@ -160,7 +168,7 @@ export default unplugin;
|
|
|
160
168
|
* Excluded ids: virtual modules (NUL prefix), `.d.ts` declaration files, and
|
|
161
169
|
* anything inside `node_modules`.
|
|
162
170
|
*/
|
|
163
|
-
function isTransformTarget(id: string): boolean {
|
|
171
|
+
export function isTransformTarget(id: string): boolean {
|
|
164
172
|
return (
|
|
165
173
|
sourceFilePattern.test(id) &&
|
|
166
174
|
!virtualModulePattern.test(id) &&
|