@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/core/transform.ts
CHANGED
|
@@ -40,15 +40,14 @@ export interface TtscTransformAlias {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
|
-
* A single entry in the
|
|
43
|
+
* A single entry in the project transform cache.
|
|
44
44
|
*
|
|
45
45
|
* Stores the full compiler result together with SHA-256 hashes of every project
|
|
46
|
-
* input file.
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* miss on every module.
|
|
46
|
+
* input file. In a cache with an explicit build lifecycle, the first delivery
|
|
47
|
+
* of each compiled module compares its supplied source with the generation
|
|
48
|
+
* snapshot in constant time; a repeated delivery re-hashes the complete input
|
|
49
|
+
* set. Persistent caches without that boundary perform complete validation on
|
|
50
|
+
* every hit.
|
|
52
51
|
*/
|
|
53
52
|
export interface TtscCachedProjectTransform {
|
|
54
53
|
/**
|
|
@@ -59,7 +58,7 @@ export interface TtscCachedProjectTransform {
|
|
|
59
58
|
* directories (`node_modules` declarations, monorepo sibling sources,
|
|
60
59
|
* out-of-root tsconfig `extends` ancestry), yet the host-owned reference
|
|
61
60
|
* graph proves they are transform inputs. Long-lived hosts that never clear
|
|
62
|
-
* the cache between builds (Metro workers
|
|
61
|
+
* the cache between builds (Metro workers and the Turbopack loader) would
|
|
63
62
|
* otherwise replay a project transform computed against a stale out-of-walk
|
|
64
63
|
* input for the whole process lifetime; per-build hosts clear the cache on
|
|
65
64
|
* `buildStart` and never replay across edits.
|
|
@@ -80,6 +79,12 @@ export interface TtscCachedProjectTransform {
|
|
|
80
79
|
projectRoot: string;
|
|
81
80
|
/** Raw compiler output returned by {@link TtscCompiler.transform}. */
|
|
82
81
|
result: ITtscCompilerTransformation;
|
|
82
|
+
/**
|
|
83
|
+
* Files already delivered from this generation, keyed by filesystem identity.
|
|
84
|
+
* Build-scoped caches use this to skip complete validation only for a
|
|
85
|
+
* module's first delivery inside the current build.
|
|
86
|
+
*/
|
|
87
|
+
servedFiles?: Set<string>;
|
|
83
88
|
/**
|
|
84
89
|
* Absolute path of the generated temp-dir tsconfig this compile ran against,
|
|
85
90
|
* when an alias/compiler-options overlay required one. The compiler reports
|
|
@@ -102,11 +107,42 @@ export type TtscTransformCache = Map<
|
|
|
102
107
|
Promise<TtscCachedProjectTransform>
|
|
103
108
|
>;
|
|
104
109
|
|
|
105
|
-
/**
|
|
110
|
+
/**
|
|
111
|
+
* Caches whose owner has declared a real per-build lifecycle by calling
|
|
112
|
+
* {@link beginTtscTransformBuild} before transforms begin.
|
|
113
|
+
*/
|
|
114
|
+
const BUILD_SCOPED_TRANSFORM_CACHES = new WeakSet<TtscTransformCache>();
|
|
115
|
+
|
|
116
|
+
/** Create an empty persistent transform cache. */
|
|
106
117
|
export function createTtscTransformCache(): TtscTransformCache {
|
|
107
118
|
return new Map();
|
|
108
119
|
}
|
|
109
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Start a host build, clearing its prior generation and enabling constant-time
|
|
123
|
+
* first delivery for modules compiled during this build.
|
|
124
|
+
*
|
|
125
|
+
* Hosts without a guaranteed build-start callback use persistent validation
|
|
126
|
+
* unless they have another immutable lifecycle. Bun runtime setup, for example,
|
|
127
|
+
* defines one process-scoped module-loading session.
|
|
128
|
+
*/
|
|
129
|
+
export function beginTtscTransformBuild(cache: TtscTransformCache): void {
|
|
130
|
+
cache.clear();
|
|
131
|
+
BUILD_SCOPED_TRANSFORM_CACHES.add(cache);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Clear a cache and return it to persistent validation mode.
|
|
136
|
+
*
|
|
137
|
+
* This is distinct from {@link beginTtscTransformBuild}: hosts such as Vite's
|
|
138
|
+
* development server may invoke `buildStart` only once for a process that spans
|
|
139
|
+
* many edits, so that callback cannot authorize build-scoped shortcuts.
|
|
140
|
+
*/
|
|
141
|
+
export function resetTtscTransformCache(cache: TtscTransformCache): void {
|
|
142
|
+
cache.clear();
|
|
143
|
+
BUILD_SCOPED_TRANSFORM_CACHES.delete(cache);
|
|
144
|
+
}
|
|
145
|
+
|
|
110
146
|
/** Cached case-insensitivity probes for existing macOS filesystem locations. */
|
|
111
147
|
const CASE_INSENSITIVE_FILESYSTEMS = new Map<string, boolean>();
|
|
112
148
|
|
|
@@ -157,8 +193,9 @@ export interface TtscTransformHooks {
|
|
|
157
193
|
* @param options - Resolved plugin options.
|
|
158
194
|
* @param aliases - Raw bundler alias configuration (Vite array or webpack
|
|
159
195
|
* object).
|
|
160
|
-
* @param cache - Optional
|
|
161
|
-
*
|
|
196
|
+
* @param cache - Optional project cache. Callers with a real `buildStart`
|
|
197
|
+
* boundary declare it through {@link beginTtscTransformBuild}; other hosts
|
|
198
|
+
* retain persistent validation.
|
|
162
199
|
* @param hooks - Optional adapter callbacks; see {@link TtscTransformHooks}.
|
|
163
200
|
* Dependency notifications fire on cache hits too; watch registrations are
|
|
164
201
|
* per build, not per compilation.
|
|
@@ -192,70 +229,90 @@ export async function transformTtsc(
|
|
|
192
229
|
tsconfig,
|
|
193
230
|
});
|
|
194
231
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
//
|
|
202
|
-
//
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
232
|
+
for (;;) {
|
|
233
|
+
let transformed = cache?.get(key);
|
|
234
|
+
if (transformed !== undefined) {
|
|
235
|
+
// A rejected in-flight generation must not stay cached: evict it (only if
|
|
236
|
+
// it is still the current entry) so a later call re-runs the transform.
|
|
237
|
+
const cached = await awaitOrEvict(cache, key, transformed);
|
|
238
|
+
// While this caller awaited the old Promise, another caller may have
|
|
239
|
+
// invalidated it and installed a newer authoritative generation.
|
|
240
|
+
if (cache?.get(key) !== transformed) {
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
if (
|
|
244
|
+
// A file the plugin declared volatile must never be served from the
|
|
245
|
+
// cache: its output depends on non-file inputs, so the input-hash
|
|
246
|
+
// snapshot cannot prove freshness. Fall through to a fresh transform.
|
|
247
|
+
!isVolatileFile({
|
|
248
|
+
file,
|
|
249
|
+
projectRoot: cached.projectRoot,
|
|
250
|
+
result: cached.result,
|
|
251
|
+
}) &&
|
|
252
|
+
matchesCachedSource(
|
|
253
|
+
cached,
|
|
254
|
+
file,
|
|
255
|
+
source,
|
|
256
|
+
cache !== undefined && BUILD_SCOPED_TRANSFORM_CACHES.has(cache),
|
|
257
|
+
)
|
|
258
|
+
) {
|
|
259
|
+
reportSuccessDiagnostics(cached.result);
|
|
260
|
+
// A resolved `"exception"` / `"failure"` envelope makes this throw;
|
|
261
|
+
// that is a failed generation too, so evict before surfacing it.
|
|
262
|
+
const code = selectOrEvict(cache, key, transformed, {
|
|
263
|
+
file,
|
|
264
|
+
projectRoot: cached.projectRoot,
|
|
265
|
+
result: cached.result,
|
|
266
|
+
});
|
|
267
|
+
notifyWatchInputs(hooks, {
|
|
268
|
+
file,
|
|
269
|
+
projectRoot: cached.projectRoot,
|
|
270
|
+
result: cached.result,
|
|
271
|
+
temporaryTsconfig: cached.temporaryTsconfig,
|
|
272
|
+
});
|
|
273
|
+
markCachedSourceServed(cached, file);
|
|
274
|
+
return createTransformResult(source, code);
|
|
275
|
+
}
|
|
276
|
+
evictGeneration(cache, key, transformed);
|
|
277
|
+
// Another caller may have replaced the generation while this caller was
|
|
278
|
+
// awaiting or validating the old one. Retry that authoritative entry
|
|
279
|
+
// instead of deleting it or starting a redundant third compilation.
|
|
280
|
+
if (cache?.get(key) !== undefined) {
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
transformed = undefined;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (transformed === undefined) {
|
|
287
|
+
transformed = transformProject({
|
|
288
|
+
aliasPaths,
|
|
289
|
+
compilerOptions: options.compilerOptions,
|
|
290
|
+
currentFile: file,
|
|
291
|
+
currentSource: source,
|
|
292
|
+
plugins: options.plugins,
|
|
293
|
+
tsconfig,
|
|
224
294
|
});
|
|
225
|
-
|
|
295
|
+
cache?.set(key, transformed);
|
|
226
296
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
tsconfig,
|
|
297
|
+
const generation = transformed;
|
|
298
|
+
const cached = await awaitOrEvict(cache, key, generation);
|
|
299
|
+
if (cache !== undefined && cache.get(key) !== generation) {
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
const { projectRoot, result, temporaryTsconfig } = cached;
|
|
303
|
+
reportSuccessDiagnostics(result);
|
|
304
|
+
const code = selectOrEvict(cache, key, generation, {
|
|
305
|
+
file,
|
|
306
|
+
projectRoot,
|
|
307
|
+
result,
|
|
239
308
|
});
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
generation,
|
|
247
|
-
);
|
|
248
|
-
reportSuccessDiagnostics(result);
|
|
249
|
-
const code = selectOrEvict(cache, key, generation, {
|
|
250
|
-
file,
|
|
251
|
-
projectRoot,
|
|
252
|
-
result,
|
|
253
|
-
});
|
|
254
|
-
notifyWatchInputs(hooks, { file, projectRoot, result, temporaryTsconfig });
|
|
255
|
-
if (isVolatileFile({ file, projectRoot, result })) {
|
|
256
|
-
hooks?.markVolatile?.();
|
|
309
|
+
notifyWatchInputs(hooks, { file, projectRoot, result, temporaryTsconfig });
|
|
310
|
+
markCachedSourceServed(cached, file);
|
|
311
|
+
if (isVolatileFile({ file, projectRoot, result })) {
|
|
312
|
+
hooks?.markVolatile?.();
|
|
313
|
+
}
|
|
314
|
+
return createTransformResult(source, code);
|
|
257
315
|
}
|
|
258
|
-
return createTransformResult(source, code);
|
|
259
316
|
}
|
|
260
317
|
|
|
261
318
|
/**
|
|
@@ -745,31 +802,30 @@ export function createTransformResult(
|
|
|
745
802
|
* Validate a cached project transform against the current on-disk project
|
|
746
803
|
* state.
|
|
747
804
|
*
|
|
748
|
-
*
|
|
749
|
-
*
|
|
750
|
-
*
|
|
751
|
-
*
|
|
752
|
-
*
|
|
753
|
-
*
|
|
754
|
-
* validated through {@link TtscCachedProjectTransform.externalInputHashes};
|
|
755
|
-
* adapters additionally register them as derived watch inputs (the host-owned
|
|
756
|
-
* `graph` union the reported `dependencies`) → `addWatchFile` → the bundler's
|
|
757
|
-
* next `buildStart` cache clear.
|
|
805
|
+
* Always compares the current module's in-memory source with the generation
|
|
806
|
+
* snapshot. A cache whose owner called {@link beginTtscTransformBuild} can use
|
|
807
|
+
* that comparison alone for the module's first delivery in the current build;
|
|
808
|
+
* repeated requests re-hash every project and out-of-walk input. Persistent
|
|
809
|
+
* caches with no guaranteed build boundary perform complete validation on every
|
|
810
|
+
* hit. Any mismatch forces a complete re-transform.
|
|
758
811
|
*
|
|
759
|
-
*
|
|
760
|
-
* exact same {@link collectProjectInputHashes} walk, so the two
|
|
761
|
-
* the key universe.
|
|
762
|
-
* keys here on only one side; those keys include out-of-walk program inputs
|
|
763
|
-
* (`node_modules` declarations, sibling-package sources), so the snapshots
|
|
764
|
-
* never matched and the cache missed on every module; re-transforming the whole
|
|
765
|
-
* project once per file on any project that imports a typed dependency.
|
|
812
|
+
* The complete validation snapshot and {@link collectInputHashes} draw their
|
|
813
|
+
* keys from the exact same {@link collectProjectInputHashes} walk, so the two
|
|
814
|
+
* agree on the key universe.
|
|
766
815
|
*/
|
|
767
816
|
function matchesCachedSource(
|
|
768
817
|
cached: TtscCachedProjectTransform,
|
|
769
818
|
file: string,
|
|
770
819
|
source: string,
|
|
820
|
+
buildScoped: boolean,
|
|
771
821
|
): boolean {
|
|
772
822
|
const currentKey = toProjectKey(cached.projectRoot, file);
|
|
823
|
+
if (cached.inputHashes[currentKey] !== hashText(source)) {
|
|
824
|
+
return false;
|
|
825
|
+
}
|
|
826
|
+
if (buildScoped && !cached.servedFiles?.has(pathIdentityKey(file))) {
|
|
827
|
+
return true;
|
|
828
|
+
}
|
|
773
829
|
const currentHashes = collectProjectInputHashes(cached.projectRoot);
|
|
774
830
|
currentHashes[currentKey] = hashText(source);
|
|
775
831
|
if (!sameHashes(cached.inputHashes, currentHashes)) {
|
|
@@ -792,6 +848,14 @@ function matchesCachedSource(
|
|
|
792
848
|
);
|
|
793
849
|
}
|
|
794
850
|
|
|
851
|
+
/** Record a successfully selected module as delivered by this generation. */
|
|
852
|
+
function markCachedSourceServed(
|
|
853
|
+
cached: TtscCachedProjectTransform,
|
|
854
|
+
file: string,
|
|
855
|
+
): void {
|
|
856
|
+
(cached.servedFiles ??= new Set()).add(pathIdentityKey(file));
|
|
857
|
+
}
|
|
858
|
+
|
|
795
859
|
/**
|
|
796
860
|
* Build the input-hash snapshot stored alongside a fresh compiler result.
|
|
797
861
|
*
|
|
@@ -876,11 +940,12 @@ function listProjectInputFiles(root: string): string[] {
|
|
|
876
940
|
|
|
877
941
|
/**
|
|
878
942
|
* Report whether an absolute `file` belongs to the project walk universe of
|
|
879
|
-
* `root`: it lies under `root
|
|
880
|
-
* the
|
|
881
|
-
* {@link listProjectInputFiles} exactly,
|
|
882
|
-
* {@link collectProjectInputHashes}".
|
|
883
|
-
*
|
|
943
|
+
* `root`: it lies under `root`, every component exists without traversing a
|
|
944
|
+
* symbolic link, the leaf is a regular file, and no segment of the relative
|
|
945
|
+
* path is ignored. The predicate mirrors {@link listProjectInputFiles} exactly,
|
|
946
|
+
* so "walk-visible" here means "hashed by {@link collectProjectInputHashes}".
|
|
947
|
+
* Missing paths and files reached through symlinks or Windows junctions are
|
|
948
|
+
* out-of-walk inputs that only the reference graph can prove relevant.
|
|
884
949
|
*/
|
|
885
950
|
export function isProjectWalkPath(root: string, file: string): boolean {
|
|
886
951
|
const relative = path.relative(pathIdentityKey(root), pathIdentityKey(file));
|
|
@@ -892,9 +957,28 @@ export function isProjectWalkPath(root: string, file: string): boolean {
|
|
|
892
957
|
) {
|
|
893
958
|
return false;
|
|
894
959
|
}
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
960
|
+
const segments = relative.split(path.sep);
|
|
961
|
+
if (segments.some(isIgnoredProjectDirectory)) {
|
|
962
|
+
return false;
|
|
963
|
+
}
|
|
964
|
+
let current = path.resolve(root);
|
|
965
|
+
for (let index = 0; index < segments.length; ++index) {
|
|
966
|
+
current = path.join(current, segments[index]!);
|
|
967
|
+
let stats: fs.Stats;
|
|
968
|
+
try {
|
|
969
|
+
stats = fs.lstatSync(current);
|
|
970
|
+
} catch {
|
|
971
|
+
return false;
|
|
972
|
+
}
|
|
973
|
+
if (stats.isSymbolicLink()) {
|
|
974
|
+
return false;
|
|
975
|
+
}
|
|
976
|
+
const leaf = index === segments.length - 1;
|
|
977
|
+
if ((leaf && !stats.isFile()) || (!leaf && !stats.isDirectory())) {
|
|
978
|
+
return false;
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
return true;
|
|
898
982
|
}
|
|
899
983
|
|
|
900
984
|
/**
|
|
@@ -1092,6 +1176,7 @@ async function transformProject(props: {
|
|
|
1092
1176
|
}),
|
|
1093
1177
|
projectRoot,
|
|
1094
1178
|
result,
|
|
1179
|
+
servedFiles: new Set(),
|
|
1095
1180
|
// Remember the generated temp-dir tsconfig (disposed below) so watch
|
|
1096
1181
|
// derivation can drop it from the envelope's config chain; a registered
|
|
1097
1182
|
// but deleted file would invalidate every persistent-cache snapshot.
|
package/src/turbopack.ts
CHANGED
|
@@ -45,9 +45,9 @@ const nodeModulesPattern = /(?:^|[/\\])node_modules(?:[/\\]|$)/;
|
|
|
45
45
|
/**
|
|
46
46
|
* Per-process transform cache. Turbopack runs loaders in a worker pool and
|
|
47
47
|
* never signals build boundaries to a loader, so the cache lives for the
|
|
48
|
-
* worker's lifetime
|
|
49
|
-
*
|
|
50
|
-
*
|
|
48
|
+
* worker's lifetime. Because no build-start boundary exists, every cache hit
|
|
49
|
+
* validates all project and graph inputs before selecting output (see
|
|
50
|
+
* `transformTtsc`).
|
|
51
51
|
*/
|
|
52
52
|
const transformCache = createTtscTransformCache();
|
|
53
53
|
|