Skip to content

Commit 2b111e6

Browse files
committed
Support loading additional TS lib files
Fixes #3726 This PR provides support for referencing other lib files that are not used by default in Deno via the compiler APIs.
1 parent 3d5bed3 commit 2b111e6

22 files changed

+284
-67
lines changed

cli/build.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,12 @@ fn op_fetch_asset(
1414
) -> impl Fn(&[u8], Option<ZeroCopyBuf>) -> CoreOp {
1515
move |control: &[u8], zero_copy_buf: Option<ZeroCopyBuf>| -> CoreOp {
1616
assert!(zero_copy_buf.is_none()); // zero_copy_buf unused in this op.
17-
let custom_assets = custom_assets.clone();
1817
let name = std::str::from_utf8(control).unwrap();
1918

2019
let asset_code = if let Some(source_code) = deno_typescript::get_asset(name)
2120
{
2221
source_code.to_string()
23-
} else if let Some(asset_path) = custom_assets.get(name) {
22+
} else if let Some(asset_path) = custom_assets.clone().get(name) {
2423
let source_code_vec =
2524
std::fs::read(&asset_path).expect("Asset not found");
2625
let source_code = std::str::from_utf8(&source_code_vec).unwrap();

cli/file_fetcher.rs

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ impl SourceFileFetcher {
165165
maybe_referrer: Option<ModuleSpecifier>,
166166
) -> Pin<Box<SourceFileFuture>> {
167167
let module_url = specifier.as_url().to_owned();
168-
debug!("fetch_source_file. specifier {} ", &module_url);
168+
debug!("fetch_source_file_async specifier: {} ", &module_url);
169169

170170
// Check if this file was already fetched and can be retrieved from in-process cache.
171171
if let Some(source_file) = self.source_file_cache.get(specifier.to_string())
@@ -368,18 +368,13 @@ impl SourceFileFetcher {
368368
}
369369
Ok(c) => c,
370370
};
371-
let media_type = map_content_type(
372-
&filepath,
373-
source_code_headers.mime_type.as_ref().map(String::as_str),
374-
);
371+
let media_type =
372+
map_content_type(&filepath, source_code_headers.mime_type.as_deref());
375373
let types_url = match media_type {
376374
msg::MediaType::JavaScript | msg::MediaType::JSX => get_types_url(
377375
&module_url,
378376
&source_code,
379-
source_code_headers
380-
.x_typescript_types
381-
.as_ref()
382-
.map(String::as_str),
377+
source_code_headers.x_typescript_types.as_deref(),
383378
),
384379
_ => None,
385380
};
@@ -515,17 +510,13 @@ impl SourceFileFetcher {
515510
.location
516511
.join(dir.deps_cache.get_cache_filename(&module_url));
517512

518-
let media_type = map_content_type(
519-
&filepath,
520-
maybe_content_type.as_ref().map(String::as_str),
521-
);
513+
let media_type =
514+
map_content_type(&filepath, maybe_content_type.as_deref());
522515

523516
let types_url = match media_type {
524-
msg::MediaType::JavaScript | msg::MediaType::JSX => get_types_url(
525-
&module_url,
526-
&source,
527-
x_typescript_types.as_ref().map(String::as_str),
528-
),
517+
msg::MediaType::JavaScript | msg::MediaType::JSX => {
518+
get_types_url(&module_url, &source, x_typescript_types.as_deref())
519+
}
529520
_ => None,
530521
};
531522

cli/js/compiler_api.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,10 @@ export interface CompilerOptions {
101101
* Does not apply to `"esnext"` target. */
102102
useDefineForClassFields?: boolean;
103103

104+
/** List of library files to be included in the compilation. If omitted,
105+
* then the Deno main runtime libs are used. */
106+
lib?: string[];
107+
104108
/** The locale to use to show error messages. */
105109
locale?: string;
106110

cli/js/compiler_api_test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,22 @@ test(async function compilerApiCompileOptions() {
4646
assert(actual["/foo.js"].startsWith("define("));
4747
});
4848

49+
test(async function compilerApiCompileLib() {
50+
const [diagnostics, actual] = await compile(
51+
"/foo.ts",
52+
{
53+
"/foo.ts": `console.log(document.getElementById("foo"));
54+
console.log(Deno.args);`
55+
},
56+
{
57+
lib: ["dom", "es2018", "deno.ns"]
58+
}
59+
);
60+
assert(diagnostics == null);
61+
assert(actual);
62+
assertEquals(Object.keys(actual), ["/foo.js.map", "/foo.js"]);
63+
});
64+
4965
test(async function transpileOnlyApi() {
5066
const actual = await transpileOnly({
5167
"foo.ts": `export enum Foo { Foo, Bar, Baz };\n`

cli/js/compiler_bootstrap.ts

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
22

3-
import { ASSETS, CompilerHostTarget, Host } from "./compiler_host.ts";
3+
import { CompilerHostTarget, Host } from "./compiler_host.ts";
4+
import { ASSETS } from "./compiler_sourcefile.ts";
45
import { getAsset } from "./compiler_util.ts";
56

67
// NOTE: target doesn't really matter here,
@@ -14,18 +15,11 @@ const options = host.getCompilationSettings();
1415

1516
// This is a hacky way of adding our libs to the libs available in TypeScript()
1617
// as these are internal APIs of TypeScript which maintain valid libs
17-
/* eslint-disable @typescript-eslint/no-explicit-any */
18-
(ts as any).libs.push(
19-
"deno_ns",
20-
"deno_window",
21-
"deno_worker",
22-
"deno_shared_globals"
23-
);
24-
(ts as any).libMap.set("deno_ns", "lib.deno.ns.d.ts");
25-
(ts as any).libMap.set("deno_window", "lib.deno.window.d.ts");
26-
(ts as any).libMap.set("deno_worker", "lib.deno.worker.d.ts");
27-
(ts as any).libMap.set("deno_shared_globals", "lib.deno.shared_globals.d.ts");
28-
/* eslint-enable @typescript-eslint/no-explicit-any */
18+
ts.libs.push("deno.ns", "deno.window", "deno.worker", "deno.shared_globals");
19+
ts.libMap.set("deno.ns", "lib.deno.ns.d.ts");
20+
ts.libMap.set("deno.window", "lib.deno.window.d.ts");
21+
ts.libMap.set("deno.worker", "lib.deno.worker.d.ts");
22+
ts.libMap.set("deno.shared_globals", "lib.deno.shared_globals.d.ts");
2923

3024
// this pre-populates the cache at snapshot time of our library files, so they
3125
// are available in the future when needed.

cli/js/compiler_host.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
22

3-
import { MediaType, SourceFile } from "./compiler_sourcefile.ts";
3+
import { ASSETS, MediaType, SourceFile } from "./compiler_sourcefile.ts";
44
import { OUT_DIR, WriteFileCallback, getAsset } from "./compiler_util.ts";
55
import { cwd } from "./dir.ts";
66
import { assert, notImplemented } from "./util.ts";
@@ -18,8 +18,14 @@ export enum CompilerHostTarget {
1818
}
1919

2020
export interface CompilerHostOptions {
21+
/** Flag determines if the host should assume a single bundle output. */
2122
bundle?: boolean;
23+
24+
/** Determines what the default library that should be used when type checking
25+
* TS code. */
2226
target: CompilerHostTarget;
27+
28+
/** A function to be used when the program emit occurs to write out files. */
2329
writeFile: WriteFileCallback;
2430
}
2531

@@ -28,8 +34,6 @@ export interface ConfigureResponse {
2834
diagnostics?: ts.Diagnostic[];
2935
}
3036

31-
export const ASSETS = "$asset$";
32-
3337
/** Options that need to be used when generating a bundle (either trusted or
3438
* runtime). */
3539
export const defaultBundlerOptions: ts.CompilerOptions = {
@@ -96,7 +100,6 @@ const ignoredCompilerOptions: readonly string[] = [
96100
"inlineSources",
97101
"init",
98102
"isolatedModules",
99-
"lib",
100103
"listEmittedFiles",
101104
"listFiles",
102105
"mapRoot",
@@ -141,7 +144,10 @@ export class Host implements ts.CompilerHost {
141144
private _writeFile: WriteFileCallback;
142145

143146
private _getAsset(filename: string): SourceFile {
144-
const url = filename.split("/").pop()!;
147+
const lastSegment = filename.split("/").pop()!;
148+
const url = ts.libMap.has(lastSegment)
149+
? ts.libMap.get(lastSegment)!
150+
: lastSegment;
145151
const sourceFile = SourceFile.get(url);
146152
if (sourceFile) {
147153
return sourceFile;
@@ -150,7 +156,7 @@ export class Host implements ts.CompilerHost {
150156
const sourceCode = getAsset(name);
151157
return new SourceFile({
152158
url,
153-
filename,
159+
filename: `${ASSETS}/${name}`,
154160
mediaType: MediaType.TypeScript,
155161
sourceCode
156162
});
@@ -230,6 +236,7 @@ export class Host implements ts.CompilerHost {
230236
}
231237

232238
getDefaultLibFileName(_options: ts.CompilerOptions): string {
239+
util.log("compiler::host.getDefaultLibFileName()");
233240
switch (this._target) {
234241
case CompilerHostTarget.Main:
235242
case CompilerHostTarget.Runtime:
@@ -259,7 +266,7 @@ export class Host implements ts.CompilerHost {
259266
if (!sourceFile.tsSourceFile) {
260267
assert(sourceFile.sourceCode != null);
261268
sourceFile.tsSourceFile = ts.createSourceFile(
262-
fileName,
269+
fileName.startsWith(ASSETS) ? sourceFile.filename : fileName,
263270
sourceFile.sourceCode,
264271
languageVersion
265272
);

cli/js/compiler_sourcefile.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ export interface SourceFileJson {
2626
sourceCode: string;
2727
}
2828

29+
export const ASSETS = "$asset$";
30+
2931
/** Returns the TypeScript Extension enum for a given media type. */
3032
function getExtension(fileName: string, mediaType: MediaType): ts.Extension {
3133
switch (mediaType) {
@@ -109,7 +111,7 @@ export class SourceFile {
109111
this.processed = true;
110112
const files = (this.importedFiles = [] as Array<[string, string]>);
111113

112-
function process(references: ts.FileReference[]): void {
114+
function process(references: Array<{ fileName: string }>): void {
113115
for (const { fileName } of references) {
114116
files.push([fileName, fileName]);
115117
}
@@ -133,7 +135,15 @@ export class SourceFile {
133135
process(importedFiles);
134136
}
135137
process(referencedFiles);
136-
process(libReferenceDirectives);
138+
// built in libs comes across as `"dom"` for example, and should be filtered
139+
// out during pre-processing as they are either already cached or they will
140+
// be lazily fetched by the compiler host. Ones that contain full files are
141+
// not filtered out and will be fetched as normal.
142+
process(
143+
libReferenceDirectives.filter(
144+
({ fileName }) => !ts.libMap.has(fileName.toLowerCase())
145+
)
146+
);
137147
process(typeReferenceDirectives);
138148
return files;
139149
}

cli/js/compiler_util.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -92,24 +92,28 @@ function cache(
9292
}
9393

9494
let OP_FETCH_ASSET: number;
95+
const encoder = new TextEncoder();
96+
const decoder = new TextDecoder();
9597

96-
/**
97-
* This op is called only during snapshotting.
98-
*
99-
* We really don't want to depend on JSON dispatch
100-
* during snapshotting, so this op exchanges strings with Rust
101-
* as raw byte arrays.
102-
*/
98+
/** Retrieve an asset from Rust. */
10399
export function getAsset(name: string): string {
100+
// this path should only be called for assets that are lazily loaded at
101+
// runtime
102+
if (dispatch.OP_FETCH_ASSET) {
103+
util.log("compiler_util::getAsset", name);
104+
return sendSync(dispatch.OP_FETCH_ASSET, { name }).sourceCode;
105+
}
106+
107+
// this path should only be taken during snapshotting
104108
if (!OP_FETCH_ASSET) {
105109
const ops = core.ops();
106110
const opFetchAsset = ops["fetch_asset"];
107111
assert(opFetchAsset, "OP_FETCH_ASSET is not registered");
108112
OP_FETCH_ASSET = opFetchAsset;
109113
}
110114

111-
const encoder = new TextEncoder();
112-
const decoder = new TextDecoder();
115+
// We really don't want to depend on JSON dispatch during snapshotting, so
116+
// this op exchanges strings with Rust as raw byte arrays.
113117
const sourceCodeBytes = core.dispatch(OP_FETCH_ASSET, encoder.encode(name));
114118
return decoder.decode(sourceCodeBytes!);
115119
}

cli/js/dispatch.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export let OP_APPLY_SOURCE_MAP: number;
1919
export let OP_FORMAT_ERROR: number;
2020
export let OP_CACHE: number;
2121
export let OP_RESOLVE_MODULES: number;
22+
export let OP_FETCH_ASSET: number;
2223
export let OP_FETCH_SOURCE_FILES: number;
2324
export let OP_OPEN: number;
2425
export let OP_CLOSE: number;
@@ -76,10 +77,6 @@ export let OP_SIGNAL_BIND: number;
7677
export let OP_SIGNAL_UNBIND: number;
7778
export let OP_SIGNAL_POLL: number;
7879

79-
/** **WARNING:** This is only available during the snapshotting process and is
80-
* unavailable at runtime. */
81-
export let OP_FETCH_ASSET: number;
82-
8380
const PLUGIN_ASYNC_HANDLER_MAP: Map<number, AsyncHandler> = new Map();
8481

8582
export function setPluginAsyncHandler(

cli/js/lib.deno.ns.d.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ declare namespace Deno {
248248
/** UNSTABLE: might move to Deno.symbols */
249249
export const EOF: unique symbol;
250250

251-
/** UNSTABLE: might move to Deno.symbols */
251+
/** UNSTABLE: might move to Deno.symbols */
252252
export type EOF = typeof EOF;
253253

254254
/** UNSTABLE: maybe remove "SEEK_" prefix. Maybe capitalization wrong. */
@@ -1917,6 +1917,10 @@ declare namespace Deno {
19171917
* Does not apply to `"esnext"` target. */
19181918
useDefineForClassFields?: boolean;
19191919

1920+
/** List of library files to be included in the compilation. If omitted,
1921+
* then the Deno main runtime libs are used. */
1922+
lib?: string[];
1923+
19201924
/** The locale to use to show error messages. */
19211925
locale?: string;
19221926

cli/js/lib.deno.shared_globals.d.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-interface, @typescript-eslint/no-explicit-any */
44

55
/// <reference no-default-lib="true" />
6-
/// <reference lib="deno_ns" />
6+
// TODO: we need to remove this, but Fetch::Response::Body implements Reader
7+
// which requires Deno.EOF, and we shouldn't be leaking that, but https_proxy
8+
// at the least requires the Reader interface on Body, which it shouldn't
9+
/// <reference lib="deno.ns" />
710
/// <reference lib="esnext" />
811

912
// https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope

cli/js/lib.deno.window.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-interface, @typescript-eslint/no-explicit-any */
44

55
/// <reference no-default-lib="true" />
6-
/// <reference lib="deno_ns" />
7-
/// <reference lib="deno_shared_globals" />
6+
/// <reference lib="deno.ns" />
7+
/// <reference lib="deno.shared_globals" />
88
/// <reference lib="esnext" />
99

1010
declare interface Window extends WindowOrWorkerGlobalScope {

cli/js/lib.deno.worker.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-interface, @typescript-eslint/no-explicit-any */
44

55
/// <reference no-default-lib="true" />
6-
/// <reference lib="deno_shared_globals" />
6+
/// <reference lib="deno.shared_globals" />
77
/// <reference lib="esnext" />
88

99
declare interface DedicatedWorkerGlobalScope extends WindowOrWorkerGlobalScope {

cli/js/ts_global.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,11 @@ declare global {
1616
namespace ts {
1717
export = ts_;
1818
}
19+
20+
namespace ts {
21+
// this are marked @internal in TypeScript, but we need to access them,
22+
// there is a risk these could change in future versions of TypeScript
23+
export const libs: string[];
24+
export const libMap: Map<string, string>;
25+
}
1926
}

0 commit comments

Comments
 (0)