-
Notifications
You must be signed in to change notification settings - Fork 41
/
transform.ts
225 lines (203 loc) · 5.9 KB
/
transform.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
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
224
225
// Copyright 2018-2024 the Deno authors. MIT license.
/**
* Lower level `transform` functionality that's used by the CLI
* to convert Deno code to Node code.
* @module
*/
import { instantiate } from "./lib/pkg/dnt_wasm.generated.js";
import type { ScriptTarget } from "./lib/types.ts";
import { valueToUrl } from "./lib/utils.ts";
/** Specifier to specifier mappings. */
export interface SpecifierMappings {
/** Map a specifier to another module or npm package. */
[specifier: string]: PackageMappedSpecifier | string;
}
export interface PackageMappedSpecifier {
/** Name of the npm package specifier to map to. */
name: string;
/** Version to use in the package.json file.
*
* Not specifying a version will exclude it from the package.json file.
* This is useful for built-in modules such as "fs".
*/
version?: string;
/** Sub path of the npm package to use in the module specifier.
*
* @remarks This should not include the package name and should not
* include a leading slash. It will be concatenated to the package
* name in the module specifier like so: `<package-name>/<sub-path>`
*/
subPath?: string;
/** If this should be a peer dependency. */
peerDependency?: boolean;
}
export interface GlobalName {
/** Name to use as the global name. */
name: string;
/** Name of the export from the package.
* @remarks Defaults to the name. Specify `"default"` to use the default export.
*/
exportName?: string;
/** Whether this is a name that only exists as a type declaration. */
typeOnly?: boolean;
}
export type Shim = PackageShim | ModuleShim;
export interface PackageShim {
/** Information about the npm package specifier to import. */
package: PackageMappedSpecifier;
/** Npm package to include in the dev depedencies that has the type declarations. */
typesPackage?: Dependency;
/** Named exports from the shim to use as globals. */
globalNames: (GlobalName | string)[];
}
export interface ModuleShim {
/** The module or bare specifier. */
module: string;
/** Named exports from the shim to use as globals. */
globalNames: (GlobalName | string)[];
}
export interface TransformOptions {
entryPoints: string[];
testEntryPoints?: string[];
shims?: Shim[];
testShims?: Shim[];
mappings?: SpecifierMappings;
target: ScriptTarget;
/// Path or url to the import map.
importMap?: string;
internalWasmUrl?: string;
}
/** Dependency in a package.json file. */
export interface Dependency {
/** Name of the package. */
name: string;
/** Version specifier (ex. `^1.0.0`). */
version: string;
/** If this is suggested to be a peer dependency. */
peerDependency?: boolean;
}
export interface TransformOutput {
main: TransformOutputEnvironment;
test: TransformOutputEnvironment;
warnings: string[];
}
export interface TransformOutputEnvironment {
entryPoints: string[];
dependencies: Dependency[];
files: OutputFile[];
}
export interface OutputFile {
filePath: string;
fileText: string;
}
/** Analyzes the provided entry point to get all the dependended on modules and
* outputs canonical TypeScript code in memory. The output of this function
* can then be sent to the TypeScript compiler or a bundler for further processing. */
export async function transform(
options: TransformOptions,
): Promise<TransformOutput> {
if (options.entryPoints.length === 0) {
throw new Error("Specify one or more entry points.");
}
const newOptions = {
...options,
mappings: Object.fromEntries(
Object.entries(options.mappings ?? {}).map(([key, value]) => {
return [valueToUrl(key), mapMappedSpecifier(value)];
}),
),
entryPoints: options.entryPoints.map(valueToUrl),
testEntryPoints: (options.testEntryPoints ?? []).map(valueToUrl),
shims: (options.shims ?? []).map(mapShim),
testShims: (options.testShims ?? []).map(mapShim),
target: options.target,
importMap: options.importMap == null
? undefined
: valueToUrl(options.importMap),
};
const wasmFuncs = await instantiate({
url: options.internalWasmUrl ? new URL(options.internalWasmUrl) : undefined,
});
return wasmFuncs.transform(newOptions);
}
type SerializableMappedSpecifier = {
kind: "package";
value: PackageMappedSpecifier;
} | {
kind: "module";
value: string;
};
function mapMappedSpecifier(
value: string | PackageMappedSpecifier,
): SerializableMappedSpecifier {
if (typeof value === "string") {
if (isPathOrUrl(value)) {
return {
kind: "module",
value: valueToUrl(value),
};
} else {
return {
kind: "package",
value: {
name: value,
},
};
}
} else {
return {
kind: "package",
value,
};
}
}
type SerializableShim = { kind: "package"; value: PackageShim } | {
kind: "module";
value: ModuleShim;
};
function mapShim(value: Shim): SerializableShim {
const newValue: Shim = {
...value,
globalNames: value.globalNames.map(mapToGlobalName),
};
if (isPackageShim(newValue)) {
return { kind: "package", value: newValue };
} else {
return {
kind: "module",
value: {
...newValue,
module: resolveBareSpecifierOrPath(newValue.module),
},
};
}
}
function isPackageShim(value: Shim): value is PackageShim {
return (value as PackageShim).package != null;
}
function mapToGlobalName(value: string | GlobalName): GlobalName {
if (typeof value === "string") {
return {
name: value,
typeOnly: false,
};
} else {
value.typeOnly ??= false;
return value;
}
}
function resolveBareSpecifierOrPath(value: string) {
value = value.trim();
if (isPathOrUrl(value)) {
return valueToUrl(value);
} else {
return value;
}
}
function isPathOrUrl(value: string) {
value = value.trim();
return /^[a-z]+:\/\//i.test(value) || // has scheme
value.startsWith("./") ||
value.startsWith("../") ||
/\.[a-z]+$/i.test(value); // has extension
}