Skip to content

Commit 59d3593

Browse files
committed
Compile TypeScript incrementally
1 parent cf5a39a commit 59d3593

File tree

7 files changed

+140
-41
lines changed

7 files changed

+140
-41
lines changed

cli/compilers/ts.rs

+20-24
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@ struct BundleResponse {
254254
struct CompileResponse {
255255
diagnostics: Diagnostic,
256256
emit_map: HashMap<String, EmittedSource>,
257+
sources: Vec<String>,
257258
}
258259

259260
// TODO(bartlomieju): possible deduplicate once TS refactor is stabilized
@@ -392,29 +393,6 @@ impl TsCompiler {
392393
return self.get_compiled_module(&source_file.url);
393394
}
394395

395-
if self.use_disk_cache {
396-
// Try to load cached version:
397-
// 1. check if there's 'meta' file
398-
if let Some(metadata) = self.get_metadata(&source_file.url) {
399-
// 2. compare version hashes
400-
// TODO: it would probably be good idea to make it method implemented on SourceFile
401-
let version_hash_to_validate = source_code_version_hash(
402-
&source_file.source_code,
403-
version::DENO,
404-
&self.config.hash,
405-
);
406-
407-
if metadata.version_hash == version_hash_to_validate {
408-
debug!("load_cache metadata version hash match");
409-
if let Ok(compiled_module) =
410-
self.get_compiled_module(&source_file.url)
411-
{
412-
self.mark_compiled(&source_file.url);
413-
return Ok(compiled_module);
414-
}
415-
}
416-
}
417-
}
418396
let source_file_ = source_file.clone();
419397
let module_url = source_file.url.clone();
420398
let target = match target {
@@ -444,6 +422,11 @@ impl TsCompiler {
444422

445423
let compile_response: CompileResponse = serde_json::from_str(json_str)?;
446424

425+
for source in compile_response.sources.iter() {
426+
let ref url = Url::parse(source)?;
427+
self.mark_compiled(url);
428+
}
429+
447430
if !compile_response.diagnostics.items.is_empty() {
448431
return Err(ErrBox::from(compile_response.diagnostics));
449432
}
@@ -484,6 +467,8 @@ impl TsCompiler {
484467
self.cache_source_map(&specifier, &source.contents)?;
485468
} else if emitted_name.ends_with(".js") {
486469
self.cache_compiled_file(&specifier, &source.contents)?;
470+
} else if emitted_name.ends_with("tsbuildinfo.json") {
471+
self.cache_build_info(&specifier, &source.contents)?;
487472
} else {
488473
panic!("Trying to cache unknown file type {}", emitted_name);
489474
}
@@ -558,7 +543,6 @@ impl TsCompiler {
558543
.disk_cache
559544
.get_cache_filename_with_extension(module_specifier.as_url(), "js");
560545
self.disk_cache.set(&js_key, contents.as_bytes())?;
561-
self.mark_compiled(module_specifier.as_url());
562546

563547
let version_hash = source_code_version_hash(
564548
&source_file.source_code,
@@ -627,6 +611,18 @@ impl TsCompiler {
627611
.get_cache_filename_with_extension(module_specifier.as_url(), "js.map");
628612
self.disk_cache.set(&source_map_key, contents.as_bytes())
629613
}
614+
615+
/// Save TS build info file to on-disk cache.
616+
fn cache_build_info(
617+
&self,
618+
module_specifier: &ModuleSpecifier,
619+
contents: &str,
620+
) -> std::io::Result<()> {
621+
let build_info_key = self
622+
.disk_cache
623+
.get_cache_filename(module_specifier.as_url());
624+
self.disk_cache.set(&build_info_key, contents.as_bytes())
625+
}
630626
}
631627

632628
impl SourceMapGetter for TsCompiler {

cli/js/compiler.ts

+13-4
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import { fromTypeScriptDiagnostic } from "./diagnostics_util.ts";
4545
import { assert } from "./util.ts";
4646
import * as util from "./util.ts";
4747
import { bootstrapWorkerRuntime } from "./runtime_worker.ts";
48+
import { SourceFile } from "./compiler/sourcefile.ts";
4849

4950
interface CompilerRequestCompile {
5051
type: CompilerRequestType.Compile;
@@ -83,6 +84,7 @@ type CompilerRequest =
8384
interface CompileResult {
8485
emitMap?: Record<string, EmmitedSource>;
8586
bundleOutput?: string;
87+
sources: string[],
8688
diagnostics: Diagnostic;
8789
}
8890

@@ -131,6 +133,7 @@ async function compile(
131133
writeFile = createCompileWriteFile(state);
132134
}
133135
const host = (state.host = new Host({
136+
rootNames,
134137
bundle,
135138
target,
136139
writeFile,
@@ -157,8 +160,9 @@ async function compile(
157160
// to generate the program and possibly emit it.
158161
if (diagnostics.length === 0) {
159162
const options = host.getCompilationSettings();
163+
const relativeRootNames = rootNames.map(host.getRelativePath.bind(host))
160164
const program = ts.createProgram({
161-
rootNames,
165+
rootNames: relativeRootNames,
162166
options,
163167
host,
164168
oldProgram: TS_SNAPSHOT_PROGRAM,
@@ -175,11 +179,15 @@ async function compile(
175179
assert(resolvedRootModules.length === 1);
176180
setRootExports(program, resolvedRootModules[0]);
177181
}
178-
const emitResult = program.emit();
179-
assert(emitResult.emitSkipped === false, "Unexpected skip of the emit.");
182+
const reportDiagnostic: ts.DiagnosticReporter = (diagnostic) =>
183+
diagnostics = !ignoredDiagnostics.includes(diagnostic.code) ? [...(diagnostics ?? []), diagnostic] : diagnostics;
184+
// @ts-ignore
185+
const exitStatus = ts.performIncrementalCompilation({ rootNames: relativeRootNames, options, host, reportDiagnostic });
186+
const emitSkipped = exitStatus === ts.ExitStatus.DiagnosticsPresent_OutputsSkipped;
187+
188+
assert(emitSkipped === false, "Unexpected skip of the emit.");
180189
// emitResult.diagnostics is `readonly` in TS3.5+ and can't be assigned
181190
// without casting.
182-
diagnostics = emitResult.diagnostics;
183191
}
184192
}
185193

@@ -194,6 +202,7 @@ async function compile(
194202
const result: CompileResult = {
195203
emitMap: state.emitMap,
196204
bundleOutput,
205+
sources: SourceFile.urls(),
197206
diagnostics: fromTypeScriptDiagnostic(diagnostics),
198207
};
199208

cli/js/compiler/host.ts

+53-10
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
22

33
import { ASSETS, MediaType, SourceFile } from "./sourcefile.ts";
4-
import { OUT_DIR, WriteFileCallback, getAsset } from "./util.ts";
4+
import { OUT_DIR, WriteFileCallback, getAsset, getCache, TS_BUILD_INFO } from "./util.ts";
55
import { assert, notImplemented } from "../util.ts";
66
import * as util from "../util.ts";
77

@@ -16,6 +16,7 @@ export interface CompilerHostOptions {
1616
target: CompilerHostTarget;
1717
unstable?: boolean;
1818
writeFile: WriteFileCallback;
19+
rootNames?: string[];
1920
}
2021

2122
export interface ConfigureResponse {
@@ -38,14 +39,17 @@ export const defaultCompileOptions: ts.CompilerOptions = {
3839
allowNonTsExtensions: true,
3940
checkJs: false,
4041
esModuleInterop: true,
42+
incremental: true,
4143
jsx: ts.JsxEmit.React,
4244
module: ts.ModuleKind.ESNext,
45+
moduleResolution: ts.ModuleResolutionKind.NodeJs,
4346
outDir: OUT_DIR,
4447
resolveJsonModule: true,
4548
sourceMap: true,
4649
strict: true,
4750
stripComments: true,
4851
target: ts.ScriptTarget.ESNext,
52+
tsBuildInfoFile: TS_BUILD_INFO,
4953
};
5054

5155
export const defaultRuntimeCompileOptions: ts.CompilerOptions = {
@@ -141,6 +145,8 @@ export class Host implements ts.CompilerHost {
141145
readonly #options = defaultCompileOptions;
142146
#target: CompilerHostTarget;
143147
#writeFile: WriteFileCallback;
148+
#rootPath = '.';
149+
#rootName = '';
144150

145151
/* Deno specific APIs */
146152

@@ -149,6 +155,7 @@ export class Host implements ts.CompilerHost {
149155
target,
150156
unstable,
151157
writeFile,
158+
rootNames
152159
}: CompilerHostOptions) {
153160
this.#target = target;
154161
this.#writeFile = writeFile;
@@ -164,6 +171,10 @@ export class Host implements ts.CompilerHost {
164171
"lib.deno.unstable.d.ts",
165172
];
166173
}
174+
if (rootNames) {
175+
this.#rootName = rootNames[0];
176+
this.#rootPath = this.#rootName.split('/').slice(0, -1).join('/');
177+
}
167178
}
168179

169180
configure(
@@ -240,6 +251,25 @@ export class Host implements ts.CompilerHost {
240251
return "\n";
241252
}
242253

254+
getAbsolutePath(fileName: string): string {
255+
//@ts-ignore
256+
return ts.resolvePath(this.#rootPath, fileName);
257+
}
258+
259+
getModuleAbsolutePath(containingFile: string, specifier: string) {
260+
//@ts-ignore
261+
return ts.resolvePath(ts.getDirectoryPath(this.getAbsolutePath(containingFile)), specifier);
262+
}
263+
264+
getRelativePath(fileName: string): string {
265+
//@ts-ignore
266+
if (!ts.pathIsAbsolute(fileName)) {
267+
return fileName;
268+
}
269+
//@ts-ignore
270+
return ts.getRelativePathFromDirectory(this.#rootPath, fileName);
271+
}
272+
243273
getSourceFile(
244274
fileName: string,
245275
languageVersion: ts.ScriptTarget,
@@ -251,21 +281,24 @@ export class Host implements ts.CompilerHost {
251281
assert(!shouldCreateNewSourceFile);
252282
const sourceFile = fileName.startsWith(ASSETS)
253283
? getAssetInternal(fileName)
254-
: SourceFile.get(fileName);
284+
: SourceFile.get(this.getAbsolutePath(fileName));
255285
assert(sourceFile != null);
256286
if (!sourceFile.tsSourceFile) {
257287
assert(sourceFile.sourceCode != null);
258288
const tsSourceFileName = fileName.startsWith(ASSETS)
259289
? sourceFile.filename
260-
: fileName;
290+
: this.getRelativePath(fileName);
261291

262292
sourceFile.tsSourceFile = ts.createSourceFile(
263293
tsSourceFileName,
264294
sourceFile.sourceCode,
265295
languageVersion
266296
);
297+
//@ts-ignore
298+
sourceFile.tsSourceFile.version = this.createHash(sourceFile.tsSourceFile.text);
267299
delete sourceFile.sourceCode;
268300
}
301+
269302
return sourceFile.tsSourceFile;
270303
} catch (e) {
271304
if (onError) {
@@ -277,8 +310,17 @@ export class Host implements ts.CompilerHost {
277310
}
278311
}
279312

280-
readFile(_fileName: string): string | undefined {
281-
return notImplemented();
313+
readFile(fileName: string): string | undefined {
314+
util.log("compiler::host.readFile", fileName);
315+
if (fileName === TS_BUILD_INFO) {
316+
return getCache(`${this.#rootName}.json`);
317+
}
318+
return getCache(this.getAbsolutePath(fileName.replace(OUT_DIR, this.#rootPath)));
319+
}
320+
321+
createHash(data: string): string {
322+
//@ts-ignore
323+
return ts.generateDjb2Hash(data);
282324
}
283325

284326
resolveModuleNames(
@@ -290,22 +332,23 @@ export class Host implements ts.CompilerHost {
290332
containingFile,
291333
});
292334
return moduleNames.map((specifier) => {
293-
const maybeUrl = SourceFile.getUrl(specifier, containingFile);
335+
const url = this.getModuleAbsolutePath(containingFile, specifier);
294336

295337
let sourceFile: SourceFile | undefined = undefined;
296338

297339
if (specifier.startsWith(ASSETS)) {
298340
sourceFile = getAssetInternal(specifier);
299-
} else if (typeof maybeUrl !== "undefined") {
300-
sourceFile = SourceFile.get(maybeUrl);
341+
} else {
342+
sourceFile = SourceFile.get(url);
301343
}
302344

303345
if (!sourceFile) {
304346
return undefined;
305347
}
306348

349+
const resolvedFileName = specifier.startsWith(ASSETS) ? sourceFile.url : this.getRelativePath(sourceFile.url);
307350
return {
308-
resolvedFileName: sourceFile.url,
351+
resolvedFileName,
309352
isExternalLibraryImport: specifier.startsWith(ASSETS),
310353
extension: sourceFile.extension,
311354
};
@@ -324,6 +367,6 @@ export class Host implements ts.CompilerHost {
324367
sourceFiles?: readonly ts.SourceFile[]
325368
): void {
326369
util.log("compiler::host.writeFile", fileName);
327-
this.#writeFile(fileName, data, sourceFiles);
370+
this.#writeFile(this.getAbsolutePath(fileName.replace(OUT_DIR, this.#rootPath)), data, sourceFiles);
328371
}
329372
}

cli/js/compiler/sourcefile.ts

+5
Original file line numberDiff line numberDiff line change
@@ -162,4 +162,9 @@ export class SourceFile {
162162
static get(url: string): SourceFile | undefined {
163163
return moduleCache.get(url);
164164
}
165+
166+
static urls(): string[] {
167+
//@ts-ignore
168+
return Array.from(moduleCache.keys()).filter(ts.pathIsAbsolute);
169+
}
165170
}

cli/js/compiler/util.ts

+16-3
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,17 @@ export enum CompilerRequestType {
4040
}
4141

4242
export const OUT_DIR = "$deno$";
43+
export const TS_BUILD_INFO = `${OUT_DIR}/tsbuildinfo.json`;
4344

4445
export function getAsset(name: string): string {
4546
return compilerOps.getAsset(name);
4647
}
4748

49+
export function getCache(name: string): string | undefined {
50+
const { content } = compilerOps.getCache(name);
51+
return content;
52+
}
53+
4854
// TODO(bartlomieju): probably could be defined inline?
4955
export function createBundleWriteFile(
5056
state: WriteFileState
@@ -68,18 +74,25 @@ export function createBundleWriteFile(
6874
export function createCompileWriteFile(
6975
state: WriteFileState
7076
): WriteFileCallback {
77+
const rootPath = state.rootNames[0].split("/").slice(0, -1).join("/");
7178
return function writeFile(
7279
fileName: string,
7380
data: string,
7481
sourceFiles?: readonly ts.SourceFile[]
7582
): void {
76-
assert(sourceFiles != null);
83+
const isBuildInfo = fileName === TS_BUILD_INFO.replace(OUT_DIR, rootPath);
84+
if (sourceFiles != null) {
85+
assert(sourceFiles.length === 1);
86+
} else {
87+
assert(isBuildInfo);
88+
}
7789
assert(state.host);
7890
assert(state.emitMap);
7991
assert(!state.bundle);
80-
assert(sourceFiles.length === 1);
92+
//@ts-ignore
93+
const filename = isBuildInfo ? `${state.rootNames[0]}.json` : ts.resolvePath(rootPath, sourceFiles[0].fileName);
8194
state.emitMap[fileName] = {
82-
filename: sourceFiles[0].fileName,
95+
filename,
8396
contents: data,
8497
};
8598
};

cli/js/ops/compiler.ts

+6
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,9 @@ export function getAsset(name: string): string {
3838
const sourceCodeBytes = core.dispatch(opId, encoder.encode(name));
3939
return decoder.decode(sourceCodeBytes!);
4040
}
41+
42+
export function getCache(
43+
url: string
44+
): { content?: string } {
45+
return sendSync("op_get_cache", { url });
46+
}

0 commit comments

Comments
 (0)