@ttsc/unplugin 0.32.0 → 0.33.0-next.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.
Files changed (93) hide show
  1. package/lib/core/options/ResolvedTtscUnpluginOptions.d.cts +2 -0
  2. package/lib/core/options/ResolvedTtscUnpluginOptions.d.mts +2 -0
  3. package/lib/core/options/ResolvedTtscUnpluginOptions.d.ts +2 -0
  4. package/lib/core/options/TtscUnpluginOptions.d.cts +12 -2
  5. package/lib/core/options/TtscUnpluginOptions.d.mts +12 -2
  6. package/lib/core/options/TtscUnpluginOptions.d.ts +12 -2
  7. package/lib/core/options/resolveOptions.d.cts +4 -3
  8. package/lib/core/options/resolveOptions.d.mts +4 -3
  9. package/lib/core/options/resolveOptions.d.ts +4 -3
  10. package/lib/core/options/resolveOptions.js +5 -3
  11. package/lib/core/options/resolveOptions.js.map +1 -1
  12. package/lib/core/options/resolveOptions.mjs +5 -3
  13. package/lib/core/options/resolveOptions.mjs.map +1 -1
  14. package/lib/core/transform/TtscPrecompilation.d.cts +52 -0
  15. package/lib/core/transform/TtscPrecompilation.d.mts +52 -0
  16. package/lib/core/transform/TtscPrecompilation.d.ts +52 -0
  17. package/lib/core/transform/TtscPrecompilation.js +101 -0
  18. package/lib/core/transform/TtscPrecompilation.js.map +1 -0
  19. package/lib/core/transform/TtscPrecompilation.mjs +101 -0
  20. package/lib/core/transform/TtscPrecompilation.mjs.map +1 -0
  21. package/lib/core/transform/cache/selectCachedGenerationAction.d.cts +2 -0
  22. package/lib/core/transform/cache/selectCachedGenerationAction.d.mts +2 -0
  23. package/lib/core/transform/cache/selectCachedGenerationAction.d.ts +2 -0
  24. package/lib/core/transform/cache/selectCachedGenerationAction.js +1 -1
  25. package/lib/core/transform/cache/selectCachedGenerationAction.js.map +1 -1
  26. package/lib/core/transform/cache/selectCachedGenerationAction.mjs +1 -1
  27. package/lib/core/transform/cache/selectCachedGenerationAction.mjs.map +1 -1
  28. package/lib/core/transform/cache/withdrawTtscTransformGenerations.js +2 -0
  29. package/lib/core/transform/cache/withdrawTtscTransformGenerations.js.map +1 -1
  30. package/lib/core/transform/cache/withdrawTtscTransformGenerations.mjs +2 -0
  31. package/lib/core/transform/cache/withdrawTtscTransformGenerations.mjs.map +1 -1
  32. package/lib/core/transform/generation/captureTransformGeneration.d.cts +2 -0
  33. package/lib/core/transform/generation/captureTransformGeneration.d.mts +2 -0
  34. package/lib/core/transform/generation/captureTransformGeneration.d.ts +2 -0
  35. package/lib/core/transform/generation/captureTransformGeneration.js +7 -2
  36. package/lib/core/transform/generation/captureTransformGeneration.js.map +1 -1
  37. package/lib/core/transform/generation/captureTransformGeneration.mjs +7 -2
  38. package/lib/core/transform/generation/captureTransformGeneration.mjs.map +1 -1
  39. package/lib/core/transform/generation/transformProject.d.cts +2 -0
  40. package/lib/core/transform/generation/transformProject.d.mts +2 -0
  41. package/lib/core/transform/generation/transformProject.d.ts +2 -0
  42. package/lib/core/transform/generation/transformProject.js.map +1 -1
  43. package/lib/core/transform/generation/transformProject.mjs.map +1 -1
  44. package/lib/core/transform/transformTtsc.d.cts +8 -24
  45. package/lib/core/transform/transformTtsc.d.mts +8 -24
  46. package/lib/core/transform/transformTtsc.d.ts +8 -24
  47. package/lib/core/transform/transformTtsc.js +51 -31
  48. package/lib/core/transform/transformTtsc.js.map +1 -1
  49. package/lib/core/transform/transformTtsc.mjs +51 -31
  50. package/lib/core/transform/transformTtsc.mjs.map +1 -1
  51. package/lib/core/transform/validation/captureUniversalHostInputValidation.d.cts +1 -1
  52. package/lib/core/transform/validation/captureUniversalHostInputValidation.d.mts +1 -1
  53. package/lib/core/transform/validation/captureUniversalHostInputValidation.d.ts +1 -1
  54. package/lib/core/transform/validation/captureUniversalHostInputValidation.js +12 -4
  55. package/lib/core/transform/validation/captureUniversalHostInputValidation.js.map +1 -1
  56. package/lib/core/transform/validation/captureUniversalHostInputValidation.mjs +12 -4
  57. package/lib/core/transform/validation/captureUniversalHostInputValidation.mjs.map +1 -1
  58. package/lib/core/transform/validation/matchesCachedSource.d.cts +9 -2
  59. package/lib/core/transform/validation/matchesCachedSource.d.mts +9 -2
  60. package/lib/core/transform/validation/matchesCachedSource.d.ts +9 -2
  61. package/lib/core/transform/validation/matchesCachedSource.js +11 -3
  62. package/lib/core/transform/validation/matchesCachedSource.js.map +1 -1
  63. package/lib/core/transform/validation/matchesCachedSource.mjs +11 -3
  64. package/lib/core/transform/validation/matchesCachedSource.mjs.map +1 -1
  65. package/lib/core/transform/watch/TtscTransformHooks.d.cts +9 -0
  66. package/lib/core/transform/watch/TtscTransformHooks.d.mts +9 -0
  67. package/lib/core/transform/watch/TtscTransformHooks.d.ts +9 -0
  68. package/lib/core/unplugin.js +14 -0
  69. package/lib/core/unplugin.js.map +1 -1
  70. package/lib/core/unplugin.mjs +14 -0
  71. package/lib/core/unplugin.mjs.map +1 -1
  72. package/lib/core/vite/installViteDependencyScanPlugins.d.cts +46 -0
  73. package/lib/core/vite/installViteDependencyScanPlugins.d.mts +46 -0
  74. package/lib/core/vite/installViteDependencyScanPlugins.d.ts +46 -0
  75. package/lib/core/vite/installViteDependencyScanPlugins.js +186 -0
  76. package/lib/core/vite/installViteDependencyScanPlugins.js.map +1 -0
  77. package/lib/core/vite/installViteDependencyScanPlugins.mjs +184 -0
  78. package/lib/core/vite/installViteDependencyScanPlugins.mjs.map +1 -0
  79. package/package.json +5 -4
  80. package/src/core/options/ResolvedTtscUnpluginOptions.ts +3 -0
  81. package/src/core/options/TtscUnpluginOptions.ts +13 -2
  82. package/src/core/options/resolveOptions.ts +6 -3
  83. package/src/core/transform/TtscPrecompilation.ts +103 -0
  84. package/src/core/transform/cache/selectCachedGenerationAction.ts +3 -0
  85. package/src/core/transform/cache/withdrawTtscTransformGenerations.ts +2 -0
  86. package/src/core/transform/generation/captureTransformGeneration.ts +12 -2
  87. package/src/core/transform/generation/transformProject.ts +3 -0
  88. package/src/core/transform/transformTtsc.ts +81 -36
  89. package/src/core/transform/validation/captureUniversalHostInputValidation.ts +12 -4
  90. package/src/core/transform/validation/matchesCachedSource.ts +9 -2
  91. package/src/core/transform/watch/TtscTransformHooks.ts +10 -0
  92. package/src/core/unplugin.ts +32 -0
  93. package/src/core/vite/installViteDependencyScanPlugins.ts +219 -0
@@ -0,0 +1,219 @@
1
+ import type {
2
+ BuildOptions,
3
+ Plugin as EsbuildPlugin,
4
+ ResolveResult,
5
+ } from "esbuild";
6
+ import fs from "node:fs";
7
+ import path from "node:path";
8
+
9
+ import { typescriptTransformSourcePattern } from "../source/typescriptTransformSourcePattern";
10
+ import type { TtscTransformResult } from "../transform/TtscTransformResult";
11
+ import { inlineSourceMap } from "../transform/utils/inlineSourceMap";
12
+ import { isHostWrapperQuery } from "../transform/utils/isHostWrapperQuery";
13
+ import { stripQuery } from "../transform/utils/stripQuery";
14
+
15
+ /**
16
+ * Exposes imports introduced by ttsc plugins to Vite's dependency scanner.
17
+ *
18
+ * Vite scans files without its normal transform hooks. Its optimizer plugins
19
+ * are the supported connection: Rolldown transforms the scanner's loaded text,
20
+ * while esbuild scans transformed modules in a separate namespace and also
21
+ * imports each original through Vite's loader. That original graph preserves
22
+ * glob, JSX and user loaders; the transformed graph adds complete relative
23
+ * import chains without a second parser or artificial exports.
24
+ *
25
+ * Vite's esbuild scanner supplies stdin; dependency bundling supplies entry
26
+ * points. Only the scanner gets the extra original graph, because publishing
27
+ * that graph would execute untransformed code alongside transformed code.
28
+ * Bundling applies ordinary transforms after user loaders instead. Supplying
29
+ * both stdin and entryPoints is not a supported Vite optimizer configuration.
30
+ *
31
+ * The caller owns compilation, aliases, validation and cache lifetime. Each
32
+ * environment keeps its own optimizer settings and existing plugins. Disabled
33
+ * discovery is left alone, and repeated installation on one settings object
34
+ * within this call is suppressed.
35
+ *
36
+ * @evidence contracts/common.md#principled-implementation Official optimizer hooks and the host's Rolldown capability select the real backend. Esbuild's stdin scan parses transformed namespaces plus actual-loader originals, while entry-point dependency bundles receive ordinary transforms without duplicate runtime source.
37
+ * @evidence contracts/common.md#clear-and-simple-design One installer selects the host's resolved optimizer backend and delegates compilation to the adapter's existing transform owner.
38
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Uses published setup, input options, namespaces, load, resolve and transform APIs. Synthetic original-import edges belong only to the unpublished dependency scan, and no host method, global, package name or expected dependency is replaced or special-cased.
39
+ * @evidence contracts/common.md#meaningful-documentation Prose explains the missing transform phase, original-loader ownership, scan versus bundle behavior, caller-owned generation validity and environment-specific option preservation.
40
+ * @evidence contracts/portability.md#os-neutral-implementation Node reads native paths and derives their resolve directories; Vite's resolver retains workspace, alias and filesystem identity policy. No OS-name or case assumption is introduced.
41
+ * @evidence contracts/performance.md#efficient-algorithms Esbuild scans at most a transformed and an original graph node per reached TypeScript source, sharing host cycle detection and parsing. Each interception delegates native resolution once. Active equal requests yield to the next resolver while the initiating interception retains the transformed graph edge; compilation and validation retain their own costs.
42
+ * @evidence contracts/performance.md#reuse-equivalent-work Scanner deliveries use the caller's transform cache and proof rules. Existing optimizer plugin objects remain shared by identity. Each distinct backend settings object receives one Rolldown transform or one prepended esbuild scanner and one appended bundle transform per installation.
43
+ * @evidence contracts/performance.md#bound-retention-and-release-resources Settings retain plugin closures for their Vite configuration lifetime. Active resolution keys are removed in finally; module graph state belongs to the host's scanner context. No child build, module history or separate compiler cache is retained, and the caller releases generations at its existing Vite boundaries.
44
+ */
45
+ export function installViteDependencyScanPlugins(
46
+ config: {
47
+ command: string;
48
+ optimizeDeps: {
49
+ disabled?: boolean | "build" | "dev";
50
+ noDiscovery?: boolean;
51
+ esbuildOptions?: BuildOptions;
52
+ rolldownOptions?: { plugins?: unknown };
53
+ };
54
+ environments?: Record<
55
+ string,
56
+ { optimizeDeps?: typeof config.optimizeDeps }
57
+ >;
58
+ },
59
+ includes: (file: string) => boolean,
60
+ transform: (
61
+ source: string,
62
+ file: string,
63
+ ) => Promise<TtscTransformResult | undefined>,
64
+ backend: "esbuild" | "rolldown",
65
+ ): void {
66
+ if (config.command !== "serve") return;
67
+ const installed = new Set<object>();
68
+ const backends = new Set<object>();
69
+ for (const options of [
70
+ config.optimizeDeps,
71
+ ...Object.values(config.environments ?? {}).map(
72
+ (environment) => environment.optimizeDeps,
73
+ ),
74
+ ]) {
75
+ if (
76
+ options === undefined ||
77
+ installed.has(options) ||
78
+ options.noDiscovery === true ||
79
+ options.disabled === true ||
80
+ options.disabled === "dev"
81
+ )
82
+ continue;
83
+ installed.add(options);
84
+ if (backend === "rolldown") {
85
+ options.rolldownOptions ??= {};
86
+ if (backends.has(options.rolldownOptions)) continue;
87
+ backends.add(options.rolldownOptions);
88
+ const existing = options.rolldownOptions.plugins;
89
+ options.rolldownOptions.plugins = [
90
+ ...(existing === undefined ? [] : [existing]),
91
+ {
92
+ name: "ttsc-dependency-scan",
93
+ async transform(source: string, file: string) {
94
+ if (isHostWrapperQuery(file) || !includes(stripQuery(file))) return;
95
+ return transform(source, stripQuery(file));
96
+ },
97
+ },
98
+ ];
99
+ continue;
100
+ }
101
+ options.esbuildOptions ??= {};
102
+ if (backends.has(options.esbuildOptions)) continue;
103
+ backends.add(options.esbuildOptions);
104
+ const plugin: EsbuildPlugin = {
105
+ name: "ttsc-dependency-scan",
106
+ setup(build) {
107
+ if (build.initialOptions.stdin === undefined) return;
108
+ const namespace = "ttsc-dependency-scan";
109
+ const originalPrefix = "ttsc-dependency-original:";
110
+ const resolving = new Set<string>();
111
+ build.onResolve(
112
+ { filter: /^ttsc-dependency-original:/, namespace },
113
+ (imported) => ({
114
+ path: imported.path.slice(originalPrefix.length),
115
+ namespace: "file",
116
+ pluginData: imported.pluginData,
117
+ }),
118
+ );
119
+ build.onResolve({ filter: /.*/ }, async (imported) => {
120
+ if (
121
+ imported.namespace !== "file" &&
122
+ imported.namespace !== namespace &&
123
+ imported.namespace !== ""
124
+ )
125
+ return;
126
+ const key = JSON.stringify([
127
+ imported.namespace === namespace
128
+ ? "file"
129
+ : imported.namespace || "file",
130
+ imported.path,
131
+ imported.importer,
132
+ imported.resolveDir,
133
+ imported.kind,
134
+ imported.with,
135
+ ]);
136
+ // Equal active requests include both self recursion and concurrent
137
+ // duplicates. Their raw edges cannot remove the first request's
138
+ // transformed node, which still exposes the added dependency closure.
139
+ if (resolving.has(key)) return;
140
+ resolving.add(key);
141
+ let resolved: ResolveResult;
142
+ try {
143
+ resolved = await build.resolve(imported.path, {
144
+ importer: imported.importer,
145
+ resolveDir: imported.resolveDir,
146
+ namespace: "file",
147
+ kind: imported.kind,
148
+ pluginData: imported.pluginData,
149
+ with: imported.with,
150
+ });
151
+ } finally {
152
+ resolving.delete(key);
153
+ }
154
+ if (
155
+ resolved.errors.length !== 0 ||
156
+ resolved.external ||
157
+ resolved.namespace !== "file" ||
158
+ !includes(resolved.path) ||
159
+ isHostWrapperQuery(resolved.suffix)
160
+ )
161
+ return resolved;
162
+ const loader =
163
+ build.initialOptions.loader?.[path.extname(resolved.path)];
164
+ if (
165
+ loader !== undefined &&
166
+ !["js", "jsx", "ts", "tsx"].includes(loader)
167
+ )
168
+ return resolved;
169
+ return { ...resolved, namespace, pluginData: resolved };
170
+ });
171
+ build.onLoad(
172
+ { filter: typescriptTransformSourcePattern, namespace },
173
+ async ({ path: file, pluginData }) => {
174
+ const resolved = pluginData as ResolveResult;
175
+ const source = await fs.promises.readFile(file, "utf8");
176
+ const transformed = await transform(source, file);
177
+ return {
178
+ contents: `${transformed?.code ?? source}\nimport ${JSON.stringify(originalPrefix + file)};\n`,
179
+ loader:
180
+ build.initialOptions.loader?.[path.extname(file)] ??
181
+ (file.endsWith(".tsx") ? "tsx" : "ts"),
182
+ resolveDir: path.dirname(file),
183
+ pluginData: resolved.pluginData,
184
+ };
185
+ },
186
+ );
187
+ },
188
+ };
189
+ const bundle: EsbuildPlugin = {
190
+ name: "ttsc-dependency-transform",
191
+ setup(build) {
192
+ if (build.initialOptions.stdin !== undefined) return;
193
+ build.onLoad(
194
+ { filter: typescriptTransformSourcePattern, namespace: "file" },
195
+ async ({ path: file }) => {
196
+ if (!includes(file)) return;
197
+ const loader =
198
+ build.initialOptions.loader?.[path.extname(file)] ??
199
+ (file.endsWith(".tsx") ? "tsx" : "ts");
200
+ if (!["js", "jsx", "ts", "tsx"].includes(loader)) return;
201
+ const source = await fs.promises.readFile(file, "utf8");
202
+ const result = await transform(source, file);
203
+ if (result === undefined) return;
204
+ return {
205
+ contents: inlineSourceMap(result),
206
+ loader,
207
+ resolveDir: path.dirname(file),
208
+ };
209
+ },
210
+ );
211
+ },
212
+ };
213
+ options.esbuildOptions.plugins = [
214
+ plugin,
215
+ ...(options.esbuildOptions.plugins ?? []),
216
+ bundle,
217
+ ];
218
+ }
219
+ }