@alchemy.run/node-utils 2.0.0-beta.76 → 2.0.0-beta.78
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/THIRD_PARTY_LICENSES.md +11 -11
- package/lib/dependency-watcher.d.ts +28 -0
- package/lib/dependency-watcher.d.ts.map +1 -0
- package/lib/dependency-watcher.js +114 -0
- package/lib/dependency-watcher.js.map +1 -0
- package/lib/register-oxc.d.ts +61 -0
- package/lib/register-oxc.d.ts.map +1 -0
- package/lib/register-oxc.js +220 -0
- package/lib/register-oxc.js.map +1 -0
- package/lib/resolve-specifier.d.ts +46 -0
- package/lib/resolve-specifier.d.ts.map +1 -0
- package/lib/resolve-specifier.js +123 -0
- package/lib/resolve-specifier.js.map +1 -0
- package/lib/transform-cache.d.ts +63 -0
- package/lib/transform-cache.d.ts.map +1 -0
- package/lib/transform-cache.js +193 -0
- package/lib/transform-cache.js.map +1 -0
- package/lib/transform-source.d.ts +19 -0
- package/lib/transform-source.d.ts.map +1 -0
- package/lib/transform-source.js +213 -0
- package/lib/transform-source.js.map +1 -0
- package/lib/watch-import-bun.d.ts +30 -0
- package/lib/watch-import-bun.d.ts.map +1 -0
- package/lib/watch-import-bun.js +73 -0
- package/lib/watch-import-bun.js.map +1 -0
- package/lib/watch-import.d.ts +27 -0
- package/lib/watch-import.d.ts.map +1 -0
- package/lib/watch-import.js +79 -0
- package/lib/watch-import.js.map +1 -0
- package/package.json +21 -2
- package/src/dependency-watcher.ts +130 -0
- package/src/register-oxc.ts +337 -0
- package/src/resolve-specifier.ts +157 -0
- package/src/transform-cache.ts +226 -0
- package/src/transform-source.ts +254 -0
- package/src/watch-import-bun.ts +99 -0
- package/src/watch-import.ts +106 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { ResolverFactory } from "rolldown/experimental";
|
|
4
|
+
export const nodeModulesSegment = `${path.sep}node_modules${path.sep}`;
|
|
5
|
+
/** Local project code: a file path outside every `node_modules` directory. */
|
|
6
|
+
export const isProjectPath = (filePath) => !filePath.includes(nodeModulesSegment);
|
|
7
|
+
const typeScriptExtensions = /\.(?:[cm]?ts|[tj]sx)$/;
|
|
8
|
+
/** Whether TypeScript's extension substitution applies to this importer. */
|
|
9
|
+
export const isTypeScriptPath = (filePath) => typeScriptExtensions.test(filePath);
|
|
10
|
+
export const isFileLikeSpecifier = (specifier) => specifier.startsWith("./") ||
|
|
11
|
+
specifier.startsWith("../") ||
|
|
12
|
+
specifier === "." ||
|
|
13
|
+
specifier === ".." ||
|
|
14
|
+
specifier.startsWith("file:") ||
|
|
15
|
+
path.isAbsolute(specifier);
|
|
16
|
+
/**
|
|
17
|
+
* Splits `?query#fragment` metadata off a specifier. Bare specifiers never
|
|
18
|
+
* carry fragments in Node, so only `?` is honoured there.
|
|
19
|
+
*/
|
|
20
|
+
export const splitSpecifierMetadata = (specifier) => {
|
|
21
|
+
const index = isFileLikeSpecifier(specifier)
|
|
22
|
+
? specifier.search(/[?#]/)
|
|
23
|
+
: specifier.indexOf("?");
|
|
24
|
+
return index === -1
|
|
25
|
+
? { specifier, metadata: "" }
|
|
26
|
+
: {
|
|
27
|
+
specifier: specifier.slice(0, index),
|
|
28
|
+
metadata: specifier.slice(index),
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
export const filePathOfUrl = (url) => {
|
|
32
|
+
if (url === undefined || !url.startsWith("file:"))
|
|
33
|
+
return undefined;
|
|
34
|
+
try {
|
|
35
|
+
return fileURLToPath(new URL(url));
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
export class SpecifierResolver {
|
|
42
|
+
#options;
|
|
43
|
+
#base;
|
|
44
|
+
#byConditions = new Map();
|
|
45
|
+
constructor(options) {
|
|
46
|
+
this.#options = {
|
|
47
|
+
tsconfig: options.tsconfig ? "auto" : undefined,
|
|
48
|
+
// TypeScript source first, then Node's implicit extensions.
|
|
49
|
+
extensions: [
|
|
50
|
+
".ts",
|
|
51
|
+
".tsx",
|
|
52
|
+
".mts",
|
|
53
|
+
".cts",
|
|
54
|
+
".jsx",
|
|
55
|
+
".js",
|
|
56
|
+
".mjs",
|
|
57
|
+
".cjs",
|
|
58
|
+
".json",
|
|
59
|
+
".node",
|
|
60
|
+
],
|
|
61
|
+
// TypeScript's emitted-extension substitution: `./x.js` may point at
|
|
62
|
+
// `x.ts` (source) or `x.js` (emitted); source wins when both exist.
|
|
63
|
+
extensionAlias: {
|
|
64
|
+
".js": [".ts", ".tsx", ".js"],
|
|
65
|
+
".jsx": [".tsx", ".jsx"],
|
|
66
|
+
".mjs": [".mts", ".mjs"],
|
|
67
|
+
".cjs": [".cts", ".cjs"],
|
|
68
|
+
},
|
|
69
|
+
mainFiles: ["index"],
|
|
70
|
+
builtinModules: true,
|
|
71
|
+
moduleType: true,
|
|
72
|
+
};
|
|
73
|
+
this.#base = new ResolverFactory(this.#options);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Bare specifiers are probed without following symlinks: a workspace
|
|
77
|
+
* package linked into `node_modules` must still read as a package (and be
|
|
78
|
+
* left to Node), not as the project file its real path points at.
|
|
79
|
+
*/
|
|
80
|
+
#resolver(conditions, symlinks) {
|
|
81
|
+
const key = `${symlinks} ${conditions.join(" ")}`;
|
|
82
|
+
let resolver = this.#byConditions.get(key);
|
|
83
|
+
if (resolver === undefined) {
|
|
84
|
+
// `cloneWithOptions` replaces the option set (sharing only the cache),
|
|
85
|
+
// so each conditions variant restates the base options.
|
|
86
|
+
resolver = this.#base.cloneWithOptions({
|
|
87
|
+
...this.#options,
|
|
88
|
+
symlinks,
|
|
89
|
+
conditionNames: [...conditions],
|
|
90
|
+
});
|
|
91
|
+
this.#byConditions.set(key, resolver);
|
|
92
|
+
}
|
|
93
|
+
return resolver;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Resolves `specifier` as imported from `parentPath` to an absolute file
|
|
97
|
+
* path, or `undefined` when Oxc cannot resolve it (Node then reports the
|
|
98
|
+
* error), when it is a builtin, or when a bare specifier lands inside
|
|
99
|
+
* `node_modules` — packages are Node's business, only `paths` aliases
|
|
100
|
+
* that map onto project files are ours.
|
|
101
|
+
*/
|
|
102
|
+
resolve(parentPath, specifier, conditions) {
|
|
103
|
+
const request = specifier.startsWith("file:")
|
|
104
|
+
? filePathOfUrl(specifier)
|
|
105
|
+
: specifier;
|
|
106
|
+
if (request === undefined)
|
|
107
|
+
return undefined;
|
|
108
|
+
let result;
|
|
109
|
+
try {
|
|
110
|
+
result = this.#resolver(conditions, isFileLikeSpecifier(request)).resolveFileSync(parentPath, request);
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
if (result.path === undefined)
|
|
116
|
+
return undefined;
|
|
117
|
+
if (!isFileLikeSpecifier(request) && !isProjectPath(result.path)) {
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
return result.path;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=resolve-specifier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-specifier.js","sourceRoot":"","sources":["../src/resolve-specifier.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export declare const TRANSFORM_CACHE_ENV = "ALCHEMY_TRANSFORM_CACHE";
|
|
2
|
+
/**
|
|
3
|
+
* The directory to use for the given option, or `undefined` when caching is
|
|
4
|
+
* off. `cache: false` and `ALCHEMY_TRANSFORM_CACHE=0` disable it; a string
|
|
5
|
+
* (option or env) names the directory; otherwise the per-user default.
|
|
6
|
+
*/
|
|
7
|
+
export declare const resolveCacheDirectory: (option: boolean | string | undefined) => string | undefined;
|
|
8
|
+
/** One transform's output as handed to {@link TransformCache.set}. */
|
|
9
|
+
export interface TransformCacheEntry {
|
|
10
|
+
readonly format: "module" | "commonjs";
|
|
11
|
+
readonly code: string;
|
|
12
|
+
/** Source map JSON, or `undefined` when the transform produced none. */
|
|
13
|
+
readonly map: string | undefined;
|
|
14
|
+
}
|
|
15
|
+
/** A cache hit: the code, and where its source map lives on disk. */
|
|
16
|
+
export interface CachedTransform {
|
|
17
|
+
readonly format: "module" | "commonjs";
|
|
18
|
+
readonly code: string;
|
|
19
|
+
/** Absolute path of the entry's `.map` file, present when it has one. */
|
|
20
|
+
readonly mapFile: string | undefined;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* On-disk cache of Oxc transform output shared by every process on the
|
|
24
|
+
* machine — the `alchemy` CLI, its dev exec child, the local-provider
|
|
25
|
+
* sidecars and dev-server runners all load the same source files, and
|
|
26
|
+
* without this each of them transpiles the whole graph again.
|
|
27
|
+
*
|
|
28
|
+
* Modelled on tsx's file cache: entries are keyed by a hash of the source
|
|
29
|
+
* file's path, size and mtime, the transform options and the resolved
|
|
30
|
+
* tsconfig, so a change to any input is simply a different key; nothing is
|
|
31
|
+
* ever invalidated in place. Size plus nanosecond mtime stands in for the
|
|
32
|
+
* contents so a hit never reads the source.
|
|
33
|
+
*
|
|
34
|
+
* An entry is two files: `<key>.json` with the code, and `<key>.map` with
|
|
35
|
+
* the source map. The map stays on disk and is referenced from the module
|
|
36
|
+
* by path rather than inlined — an inline `data:` map is part of the
|
|
37
|
+
* script's source text, which V8 keeps for the process lifetime; for a
|
|
38
|
+
* graph the size of alchemy's that is hundreds of megabytes per process.
|
|
39
|
+
* Node reads the referenced file synchronously as it compiles the module,
|
|
40
|
+
* so the map is written (atomically) before `set` returns; the code entry
|
|
41
|
+
* is a fire-and-forget write, because a missing one is only a slower
|
|
42
|
+
* cache. Reads are synchronous (the loader hook is). Stale entries are
|
|
43
|
+
* swept by age once per process.
|
|
44
|
+
*/
|
|
45
|
+
export declare class TransformCache {
|
|
46
|
+
#private;
|
|
47
|
+
constructor(directory: string);
|
|
48
|
+
/** The entry key for these transform inputs. */
|
|
49
|
+
key(parts: ReadonlyArray<string>): string;
|
|
50
|
+
/**
|
|
51
|
+
* The cached transform, or `undefined` on a miss. An entry whose map file
|
|
52
|
+
* has gone (swept, or never landed) is a miss too: the module would
|
|
53
|
+
* otherwise reference a map that is not there.
|
|
54
|
+
*/
|
|
55
|
+
get(key: string): CachedTransform | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* Stores one transform. Returns the map file's path once it is on disk,
|
|
58
|
+
* or `undefined` when there is no map or it could not be written — the
|
|
59
|
+
* caller then falls back to inlining it.
|
|
60
|
+
*/
|
|
61
|
+
set(key: string, entry: TransformCacheEntry): string | undefined;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=transform-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transform-cache.d.ts","sourceRoot":"","sources":["../src/transform-cache.ts"],"names":[],"mappings":"AAyBA,eAAO,MAAM,mBAAmB,4BAA4B,CAAC;AAW7D;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,WACxB,OAAO,GAAG,MAAM,GAAG,SAAS,KACnC,MAAM,GAAG,SAOX,CAAC;AAEF,sEAAsE;AACtE,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,MAAM,EAAE,QAAQ,GAAG,UAAU,CAAC;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,wEAAwE;IACxE,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;CAClC;AAED,qEAAqE;AACrE,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE,QAAQ,GAAG,UAAU,CAAC;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,yEAAyE;IACzE,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;CACtC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,cAAc;;IAKzB,YAAY,SAAS,EAAE,MAAM,EAE5B;IAED,gDAAgD;IAChD,GAAG,CAAC,KAAK,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,MAAM,CASxC;IAED;;;;OAIG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CA4B5C;IAED;;;;OAIG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB,GAAG,MAAM,GAAG,SAAS,CAqB/D;CAoDF"}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { existsSync, mkdirSync, promises as fs, readFileSync, renameSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import rolldown from "rolldown/package.json" with { type: "json" };
|
|
6
|
+
import self from "../package.json" with { type: "json" };
|
|
7
|
+
/**
|
|
8
|
+
* Anything that changes Oxc's output for identical input invalidates every
|
|
9
|
+
* entry: the transformer itself (rolldown), this package's transform
|
|
10
|
+
* pipeline, and the on-disk entry layout.
|
|
11
|
+
*/
|
|
12
|
+
const CACHE_VERSION = ["1", self.version, rolldown.version].join("-");
|
|
13
|
+
/** Entries untouched for this long are swept on the first disk access. */
|
|
14
|
+
const MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
15
|
+
export const TRANSFORM_CACHE_ENV = "ALCHEMY_TRANSFORM_CACHE";
|
|
16
|
+
/**
|
|
17
|
+
* Per-user directory, like tsx's `tsx-<uid>`: `tmpdir()` is shared on
|
|
18
|
+
* multi-user machines, and entries are written with the owner's umask.
|
|
19
|
+
*/
|
|
20
|
+
const defaultDirectory = () => {
|
|
21
|
+
const user = process.geteuid?.() ?? os.userInfo().username;
|
|
22
|
+
return path.join(os.tmpdir(), `alchemy-oxc-${user}`);
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* The directory to use for the given option, or `undefined` when caching is
|
|
26
|
+
* off. `cache: false` and `ALCHEMY_TRANSFORM_CACHE=0` disable it; a string
|
|
27
|
+
* (option or env) names the directory; otherwise the per-user default.
|
|
28
|
+
*/
|
|
29
|
+
export const resolveCacheDirectory = (option) => {
|
|
30
|
+
if (option === false)
|
|
31
|
+
return undefined;
|
|
32
|
+
if (typeof option === "string")
|
|
33
|
+
return option;
|
|
34
|
+
const env = process.env[TRANSFORM_CACHE_ENV];
|
|
35
|
+
if (env === "0" || env === "false")
|
|
36
|
+
return undefined;
|
|
37
|
+
if (env !== undefined && env !== "")
|
|
38
|
+
return env;
|
|
39
|
+
return defaultDirectory();
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* On-disk cache of Oxc transform output shared by every process on the
|
|
43
|
+
* machine — the `alchemy` CLI, its dev exec child, the local-provider
|
|
44
|
+
* sidecars and dev-server runners all load the same source files, and
|
|
45
|
+
* without this each of them transpiles the whole graph again.
|
|
46
|
+
*
|
|
47
|
+
* Modelled on tsx's file cache: entries are keyed by a hash of the source
|
|
48
|
+
* file's path, size and mtime, the transform options and the resolved
|
|
49
|
+
* tsconfig, so a change to any input is simply a different key; nothing is
|
|
50
|
+
* ever invalidated in place. Size plus nanosecond mtime stands in for the
|
|
51
|
+
* contents so a hit never reads the source.
|
|
52
|
+
*
|
|
53
|
+
* An entry is two files: `<key>.json` with the code, and `<key>.map` with
|
|
54
|
+
* the source map. The map stays on disk and is referenced from the module
|
|
55
|
+
* by path rather than inlined — an inline `data:` map is part of the
|
|
56
|
+
* script's source text, which V8 keeps for the process lifetime; for a
|
|
57
|
+
* graph the size of alchemy's that is hundreds of megabytes per process.
|
|
58
|
+
* Node reads the referenced file synchronously as it compiles the module,
|
|
59
|
+
* so the map is written (atomically) before `set` returns; the code entry
|
|
60
|
+
* is a fire-and-forget write, because a missing one is only a slower
|
|
61
|
+
* cache. Reads are synchronous (the loader hook is). Stale entries are
|
|
62
|
+
* swept by age once per process.
|
|
63
|
+
*/
|
|
64
|
+
export class TransformCache {
|
|
65
|
+
#directory;
|
|
66
|
+
#ready = false;
|
|
67
|
+
#sequence = 0;
|
|
68
|
+
constructor(directory) {
|
|
69
|
+
this.#directory = directory;
|
|
70
|
+
}
|
|
71
|
+
/** The entry key for these transform inputs. */
|
|
72
|
+
key(parts) {
|
|
73
|
+
const hash = createHash("sha1");
|
|
74
|
+
hash.update(CACHE_VERSION);
|
|
75
|
+
for (const part of parts) {
|
|
76
|
+
// Length-prefixed so adjacent parts cannot run into each other.
|
|
77
|
+
hash.update(`\0${part.length}\0`);
|
|
78
|
+
hash.update(part);
|
|
79
|
+
}
|
|
80
|
+
return hash.digest("hex");
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The cached transform, or `undefined` on a miss. An entry whose map file
|
|
84
|
+
* has gone (swept, or never landed) is a miss too: the module would
|
|
85
|
+
* otherwise reference a map that is not there.
|
|
86
|
+
*/
|
|
87
|
+
get(key) {
|
|
88
|
+
this.#prepare();
|
|
89
|
+
let raw;
|
|
90
|
+
try {
|
|
91
|
+
raw = readFileSync(this.#file(key, "json"), "utf8");
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
let entry;
|
|
97
|
+
try {
|
|
98
|
+
entry = JSON.parse(raw);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
if ((entry.format !== "module" && entry.format !== "commonjs") ||
|
|
104
|
+
typeof entry.code !== "string" ||
|
|
105
|
+
typeof entry.map !== "boolean") {
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
const mapFile = this.#file(key, "map");
|
|
109
|
+
if (entry.map && !existsSync(mapFile))
|
|
110
|
+
return undefined;
|
|
111
|
+
return {
|
|
112
|
+
format: entry.format,
|
|
113
|
+
code: entry.code,
|
|
114
|
+
mapFile: entry.map ? mapFile : undefined,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Stores one transform. Returns the map file's path once it is on disk,
|
|
119
|
+
* or `undefined` when there is no map or it could not be written — the
|
|
120
|
+
* caller then falls back to inlining it.
|
|
121
|
+
*/
|
|
122
|
+
set(key, entry) {
|
|
123
|
+
this.#prepare();
|
|
124
|
+
let mapFile;
|
|
125
|
+
if (entry.map !== undefined) {
|
|
126
|
+
mapFile = this.#file(key, "map");
|
|
127
|
+
if (!this.#writeAtomically(mapFile, entry.map))
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
const file = this.#file(key, "json");
|
|
131
|
+
const temporary = this.#temporary(file);
|
|
132
|
+
// Best effort: a cache that cannot be written is only a slower cache.
|
|
133
|
+
fs.writeFile(temporary, JSON.stringify({
|
|
134
|
+
format: entry.format,
|
|
135
|
+
code: entry.code,
|
|
136
|
+
map: mapFile !== undefined,
|
|
137
|
+
}))
|
|
138
|
+
.then(() => fs.rename(temporary, file))
|
|
139
|
+
.catch(() => fs.unlink(temporary).catch(() => { }));
|
|
140
|
+
return mapFile;
|
|
141
|
+
}
|
|
142
|
+
#file(key, extension) {
|
|
143
|
+
return path.join(this.#directory, `${key}.${extension}`);
|
|
144
|
+
}
|
|
145
|
+
#temporary(file) {
|
|
146
|
+
return `${file}.${process.pid}.${this.#sequence++}.tmp`;
|
|
147
|
+
}
|
|
148
|
+
/** Temp file plus rename: concurrent readers never see a partial file. */
|
|
149
|
+
#writeAtomically(file, content) {
|
|
150
|
+
const temporary = this.#temporary(file);
|
|
151
|
+
try {
|
|
152
|
+
writeFileSync(temporary, content);
|
|
153
|
+
renameSync(temporary, file);
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
try {
|
|
158
|
+
unlinkSync(temporary);
|
|
159
|
+
}
|
|
160
|
+
catch { }
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
#prepare() {
|
|
165
|
+
if (this.#ready)
|
|
166
|
+
return;
|
|
167
|
+
this.#ready = true;
|
|
168
|
+
try {
|
|
169
|
+
mkdirSync(this.#directory, { recursive: true });
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
// Off the hot path: the loader hook that got us here is synchronous.
|
|
175
|
+
setImmediate(() => {
|
|
176
|
+
this.#sweep().catch(() => { });
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
async #sweep() {
|
|
180
|
+
const cutoff = Date.now() - MAX_AGE_MS;
|
|
181
|
+
const names = await fs.readdir(this.#directory);
|
|
182
|
+
await Promise.all(names.map(async (name) => {
|
|
183
|
+
const file = path.join(this.#directory, name);
|
|
184
|
+
try {
|
|
185
|
+
const { mtimeMs } = await fs.stat(file);
|
|
186
|
+
if (mtimeMs < cutoff)
|
|
187
|
+
await fs.unlink(file);
|
|
188
|
+
}
|
|
189
|
+
catch { }
|
|
190
|
+
}));
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
//# sourceMappingURL=transform-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transform-cache.js","sourceRoot":"","sources":["../src/transform-cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { OxcLoaderOptions } from "./register-oxc.ts";
|
|
2
|
+
/** Extensions Oxc transpiles; everything else is JavaScript Node can run. */
|
|
3
|
+
export declare const transformExtensions: Set<string>;
|
|
4
|
+
export type ModuleFormat = "module" | "commonjs";
|
|
5
|
+
export interface TransformedSource {
|
|
6
|
+
readonly format: ModuleFormat;
|
|
7
|
+
readonly source: string;
|
|
8
|
+
}
|
|
9
|
+
export declare class SourceTransformer {
|
|
10
|
+
#private;
|
|
11
|
+
constructor(options: OxcLoaderOptions);
|
|
12
|
+
/**
|
|
13
|
+
* Transpiles `filePath` for Node, or returns `undefined` when the file is
|
|
14
|
+
* JavaScript that needs no work. `format` is what Node's `load` hook was
|
|
15
|
+
* told; it decides `sourceType` and the format handed back.
|
|
16
|
+
*/
|
|
17
|
+
transform(filePath: string, format: string | null | undefined): TransformedSource | undefined;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=transform-source.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transform-source.d.ts","sourceRoot":"","sources":["../src/transform-source.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAG1D,6EAA6E;AAC7E,eAAO,MAAM,mBAAmB,aAM9B,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,UAAU,CAAC;AA0FjD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,qBAAa,iBAAiB;;IAK5B,YAAY,OAAO,EAAE,gBAAgB,EAKpC;IA4CD;;;;OAIG;IACH,SAAS,CACP,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAChC,iBAAiB,GAAG,SAAS,CAyE/B;CACF"}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
import { resolveTsconfig } from "rolldown/experimental";
|
|
5
|
+
import { parseSync, transformSync, TsconfigCache, } from "rolldown/utils";
|
|
6
|
+
import { resolveCacheDirectory, TransformCache } from "./transform-cache.js";
|
|
7
|
+
/** Extensions Oxc transpiles; everything else is JavaScript Node can run. */
|
|
8
|
+
export const transformExtensions = new Set([
|
|
9
|
+
".ts",
|
|
10
|
+
".tsx",
|
|
11
|
+
".mts",
|
|
12
|
+
".cts",
|
|
13
|
+
".jsx",
|
|
14
|
+
]);
|
|
15
|
+
/**
|
|
16
|
+
* Module format from Node's `load` hook context. Node derives these from the
|
|
17
|
+
* extension and the nearest `package.json#type`; `*-typescript` variants are
|
|
18
|
+
* its TypeScript-aware spellings and mean the same thing.
|
|
19
|
+
*/
|
|
20
|
+
const nodeFormat = (format) => {
|
|
21
|
+
switch (format) {
|
|
22
|
+
case "module":
|
|
23
|
+
case "module-typescript":
|
|
24
|
+
return "module";
|
|
25
|
+
case "commonjs":
|
|
26
|
+
case "commonjs-typescript":
|
|
27
|
+
return "commonjs";
|
|
28
|
+
default:
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
/** Fallback for older Nodes that pass no format: extension, then package type. */
|
|
33
|
+
const inferFormat = (filePath) => {
|
|
34
|
+
const extension = path.extname(filePath);
|
|
35
|
+
if (extension === ".mts" || extension === ".mjs")
|
|
36
|
+
return "module";
|
|
37
|
+
if (extension === ".cts" || extension === ".cjs")
|
|
38
|
+
return "commonjs";
|
|
39
|
+
let directory = path.dirname(filePath);
|
|
40
|
+
while (true) {
|
|
41
|
+
const packageJson = path.join(directory, "package.json");
|
|
42
|
+
if (existsSync(packageJson)) {
|
|
43
|
+
try {
|
|
44
|
+
return JSON.parse(readFileSync(packageJson, "utf8")).type === "module"
|
|
45
|
+
? "module"
|
|
46
|
+
: "commonjs";
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return "commonjs";
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const parent = path.dirname(directory);
|
|
53
|
+
if (parent === directory)
|
|
54
|
+
return "commonjs";
|
|
55
|
+
directory = parent;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
const language = (filePath) => {
|
|
59
|
+
switch (path.extname(filePath)) {
|
|
60
|
+
case ".tsx":
|
|
61
|
+
return "tsx";
|
|
62
|
+
case ".ts":
|
|
63
|
+
case ".mts":
|
|
64
|
+
case ".cts":
|
|
65
|
+
return "ts";
|
|
66
|
+
case ".jsx":
|
|
67
|
+
return "jsx";
|
|
68
|
+
default:
|
|
69
|
+
return "js";
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Inline map, for when there is no cache file to point at. Only the
|
|
74
|
+
* fallback: the base64 becomes part of the script source V8 retains, and
|
|
75
|
+
* for a non-ASCII source it is stored two bytes per character on top.
|
|
76
|
+
*/
|
|
77
|
+
const inlineSourceMapComment = (map) => {
|
|
78
|
+
const json = typeof map === "string" ? map : JSON.stringify(map);
|
|
79
|
+
return `\n//# sourceMappingURL=data:application/json;base64,${Buffer.from(json).toString("base64")}`;
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Map by reference. Node's source-map support only understands `data:`
|
|
83
|
+
* URLs and scheme-less paths (it resolves the latter against the module
|
|
84
|
+
* URL and reads the file), so this is the file URL's path component —
|
|
85
|
+
* `/var/…/x.map` on POSIX, `/C:/…/x.map` on Windows — never a `file:` URL.
|
|
86
|
+
*/
|
|
87
|
+
const fileSourceMapComment = (mapFile) => `\n//# sourceMappingURL=${pathToFileURL(mapFile).pathname}`;
|
|
88
|
+
/**
|
|
89
|
+
* The transform's map without `sourcesContent`. Every source is a file on
|
|
90
|
+
* this machine, named by the map's `sources`, so embedding its text only
|
|
91
|
+
* makes the map larger than the code it describes and every process that
|
|
92
|
+
* loads the module pay for it.
|
|
93
|
+
*/
|
|
94
|
+
const withoutSourcesContent = ({ sourcesContent: _, ...map }) => map;
|
|
95
|
+
export class SourceTransformer {
|
|
96
|
+
#options;
|
|
97
|
+
#tsconfigCache = new TsconfigCache();
|
|
98
|
+
#cache;
|
|
99
|
+
constructor(options) {
|
|
100
|
+
this.#options = options;
|
|
101
|
+
const directory = resolveCacheDirectory(options.cache);
|
|
102
|
+
this.#cache =
|
|
103
|
+
directory === undefined ? undefined : new TransformCache(directory);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Cache key for one transform, or `undefined` when the result must not be
|
|
107
|
+
* cached. Every input Oxc's output depends on is part of it: the file's
|
|
108
|
+
* path (source maps name it), its size and mtime standing in for its
|
|
109
|
+
* contents — a stat instead of a read plus a hash per module on the warm
|
|
110
|
+
* path — the effective transform options, Node's module format, and the
|
|
111
|
+
* tsconfig that would be discovered for the file, resolved through the
|
|
112
|
+
* same cache `transformSync` uses with the `extends` chain already
|
|
113
|
+
* merged, so editing any tsconfig in the chain is a new key.
|
|
114
|
+
*/
|
|
115
|
+
#cacheKey(filePath, options, format) {
|
|
116
|
+
if (this.#cache === undefined)
|
|
117
|
+
return undefined;
|
|
118
|
+
let stat;
|
|
119
|
+
try {
|
|
120
|
+
stat = statSync(filePath, { bigint: true });
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
let tsconfig = null;
|
|
126
|
+
try {
|
|
127
|
+
if (options.tsconfig === true) {
|
|
128
|
+
tsconfig = resolveTsconfig(filePath, this.#tsconfigCache)?.tsconfig;
|
|
129
|
+
}
|
|
130
|
+
else if (typeof options.tsconfig === "string") {
|
|
131
|
+
tsconfig = readFileSync(options.tsconfig, "utf8");
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
// A broken tsconfig is the transform's error to report; don't cache.
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
return this.#cache.key([
|
|
139
|
+
filePath,
|
|
140
|
+
`${stat.size}:${stat.mtimeNs}`,
|
|
141
|
+
JSON.stringify(options),
|
|
142
|
+
JSON.stringify(tsconfig ?? null),
|
|
143
|
+
format,
|
|
144
|
+
]);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Transpiles `filePath` for Node, or returns `undefined` when the file is
|
|
148
|
+
* JavaScript that needs no work. `format` is what Node's `load` hook was
|
|
149
|
+
* told; it decides `sourceType` and the format handed back.
|
|
150
|
+
*/
|
|
151
|
+
transform(filePath, format) {
|
|
152
|
+
const extension = path.extname(filePath);
|
|
153
|
+
if (!transformExtensions.has(extension))
|
|
154
|
+
return undefined;
|
|
155
|
+
let moduleFormat = nodeFormat(format) ?? inferFormat(filePath);
|
|
156
|
+
const lang = language(filePath);
|
|
157
|
+
const options = {
|
|
158
|
+
tsconfig: this.#options.tsconfig ?? true,
|
|
159
|
+
sourcemap: true,
|
|
160
|
+
lang,
|
|
161
|
+
};
|
|
162
|
+
// The key is taken before the source is read so a hit costs one stat
|
|
163
|
+
// and one cache read, never the source itself.
|
|
164
|
+
const key = this.#cacheKey(filePath, options, moduleFormat);
|
|
165
|
+
const cached = key === undefined ? undefined : this.#cache?.get(key);
|
|
166
|
+
if (cached !== undefined) {
|
|
167
|
+
return {
|
|
168
|
+
format: cached.format,
|
|
169
|
+
source: cached.mapFile === undefined
|
|
170
|
+
? cached.code
|
|
171
|
+
: cached.code + fileSourceMapComment(cached.mapFile),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
const source = readFileSync(filePath, "utf8");
|
|
175
|
+
// A `.ts` file in a CommonJS package that uses `import`/`export` runs
|
|
176
|
+
// as ESM — the same call Node's own module-syntax detection makes for
|
|
177
|
+
// `.js`. Explicit `.cts` stays CommonJS regardless.
|
|
178
|
+
if (moduleFormat === "commonjs" &&
|
|
179
|
+
extension !== ".cts" &&
|
|
180
|
+
parseSync(filePath, source, { lang, sourceType: "unambiguous" }).module
|
|
181
|
+
.hasModuleSyntax) {
|
|
182
|
+
moduleFormat = "module";
|
|
183
|
+
}
|
|
184
|
+
const transformed = transformSync(filePath, source, { ...options, sourceType: moduleFormat }, this.#tsconfigCache);
|
|
185
|
+
if (transformed.errors.length > 0) {
|
|
186
|
+
const [error] = transformed.errors;
|
|
187
|
+
throw error instanceof Error
|
|
188
|
+
? error
|
|
189
|
+
: new SyntaxError(`${filePath}: ${error.message ?? String(error)}`);
|
|
190
|
+
}
|
|
191
|
+
const map = transformed.map === undefined
|
|
192
|
+
? undefined
|
|
193
|
+
: JSON.stringify(withoutSourcesContent(transformed.map));
|
|
194
|
+
// The map ends up in the module exactly one way: on disk next to the
|
|
195
|
+
// cache entry and referenced by path, or (cache off) inlined.
|
|
196
|
+
const mapFile = key === undefined || map === undefined
|
|
197
|
+
? undefined
|
|
198
|
+
: this.#cache?.set(key, {
|
|
199
|
+
format: moduleFormat,
|
|
200
|
+
code: transformed.code,
|
|
201
|
+
map,
|
|
202
|
+
});
|
|
203
|
+
return {
|
|
204
|
+
format: moduleFormat,
|
|
205
|
+
source: mapFile !== undefined
|
|
206
|
+
? transformed.code + fileSourceMapComment(mapFile)
|
|
207
|
+
: map !== undefined
|
|
208
|
+
? transformed.code + inlineSourceMapComment(map)
|
|
209
|
+
: transformed.code,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
//# sourceMappingURL=transform-source.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transform-source.js","sourceRoot":"","sources":["../src/transform-source.ts"],"names":[],"mappings":"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"}
|