From da8cb408c878aa6e90542e26173f1f14b5254d29 Mon Sep 17 00:00:00 2001 From: Kitson Kelly Date: Thu, 19 Mar 2020 03:39:53 +1100 Subject: [PATCH] Provide compiled JSON to TypeScript compiler. (#4404) Fixes #4101 Previously, we would just provide the raw JSON to the TypeScript compiler worker, but TypeScript does not transform JSON. This caused a problem when emitting a bundle, that the JSON would just be "inlined" into the output, instead of being transformed into a module. This fixes this problem by providing the compiled JSON to the TypeScript compiler, so TypeScript just sees JSON as a "normal" TypeScript module. --- cli/js/compiler/host.ts | 10 +++++++++- cli/js/compiler/sourcefile.ts | 5 ++++- cli/js/compiler/util.ts | 6 +++--- cli/ops/compiler.rs | 12 ++++++++++-- cli/tests/integration_tests.rs | 34 ++++++++++++++++++++++++++++++++++ 5 files changed, 60 insertions(+), 7 deletions(-) diff --git a/cli/js/compiler/host.ts b/cli/js/compiler/host.ts index 627c529702..457388bd9a 100644 --- a/cli/js/compiler/host.ts +++ b/cli/js/compiler/host.ts @@ -242,8 +242,16 @@ export class Host implements ts.CompilerHost { assert(sourceFile != null); if (!sourceFile.tsSourceFile) { assert(sourceFile.sourceCode != null); + // even though we assert the extension for JSON modules to the compiler + // is TypeScript, TypeScript internally analyses the filename for its + // extension and tries to parse it as JSON instead of TS. We have to + // change the filename to the TypeScript file. sourceFile.tsSourceFile = ts.createSourceFile( - fileName.startsWith(ASSETS) ? sourceFile.filename : fileName, + fileName.startsWith(ASSETS) + ? sourceFile.filename + : fileName.toLowerCase().endsWith(".json") + ? `${fileName}.ts` + : fileName, sourceFile.sourceCode, languageVersion ); diff --git a/cli/js/compiler/sourcefile.ts b/cli/js/compiler/sourcefile.ts index 159ccda857..e400acbf56 100644 --- a/cli/js/compiler/sourcefile.ts +++ b/cli/js/compiler/sourcefile.ts @@ -35,7 +35,10 @@ function getExtension(fileName: string, mediaType: MediaType): ts.Extension { case MediaType.TSX: return ts.Extension.Tsx; case MediaType.Json: - return ts.Extension.Json; + // we internally compile JSON, so what gets provided to the TypeScript + // compiler is an ES module, but in order to get TypeScript to handle it + // properly we have to pretend it is TS. + return ts.Extension.Ts; case MediaType.Wasm: // Custom marker for Wasm type. return ts.Extension.Js; diff --git a/cli/js/compiler/util.ts b/cli/js/compiler/util.ts index 09725fc223..8acc83a2d9 100644 --- a/cli/js/compiler/util.ts +++ b/cli/js/compiler/util.ts @@ -4,7 +4,7 @@ import { bold, cyan, yellow } from "../colors.ts"; import { CompilerOptions } from "./api.ts"; import { buildBundle } from "./bundler.ts"; import { ConfigureResponse, Host } from "./host.ts"; -import { SourceFile } from "./sourcefile.ts"; +import { MediaType, SourceFile } from "./sourcefile.ts"; import { atob, TextEncoder } from "../web/text_encoding.ts"; import * as compilerOps from "../ops/compiler.ts"; import * as util from "../util.ts"; @@ -51,13 +51,13 @@ function cache( // NOTE: If it's a `.json` file we don't want to write it to disk. // JSON files are loaded and used by TS compiler to check types, but we don't want // to emit them to disk because output file is the same as input file. - if (sf.extension === ts.Extension.Json) { + if (sf.mediaType === MediaType.Json) { return; } // NOTE: JavaScript files are only cached to disk if `checkJs` // option in on - if (sf.extension === ts.Extension.Js && !checkJs) { + if (sf.mediaType === MediaType.JavaScript && !checkJs) { return; } } diff --git a/cli/ops/compiler.rs b/cli/ops/compiler.rs index e6ed364dab..baa7b4c1c1 100644 --- a/cli/ops/compiler.rs +++ b/cli/ops/compiler.rs @@ -136,9 +136,9 @@ fn op_fetch_source_files( } _ => f, }; - // Special handling of Wasm files: + // Special handling of WASM and JSON files: // compile them into JS first! - // This allows TS to do correct export types. + // This allows TS to do correct export types as well as bundles. let source_code = match file.media_type { msg::MediaType::Wasm => { global_state @@ -148,6 +148,14 @@ fn op_fetch_source_files( .map_err(|e| OpError::other(e.to_string()))? .code } + msg::MediaType::Json => { + global_state + .json_compiler + .compile(&file) + .await + .map_err(|e| OpError::other(e.to_string()))? + .code + } _ => String::from_utf8(file.source_code).unwrap(), }; Ok::<_, OpError>(json!({ diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs index 9cdf0af563..29203d411b 100644 --- a/cli/tests/integration_tests.rs +++ b/cli/tests/integration_tests.rs @@ -398,6 +398,40 @@ fn bundle_single_module() { assert_eq!(output.stderr, b""); } +#[test] +fn bundle_json() { + use tempfile::TempDir; + + let json_modules = util::root_path().join("cli/tests/020_json_modules.ts"); + assert!(json_modules.is_file()); + let t = TempDir::new().expect("tempdir fail"); + let bundle = t.path().join("020_json_modules.bundle.js"); + let mut deno = util::deno_cmd() + .current_dir(util::root_path()) + .arg("bundle") + .arg(json_modules) + .arg(&bundle) + .spawn() + .expect("failed to spawn script"); + let status = deno.wait().expect("failed to wait for the child process"); + assert!(status.success()); + assert!(bundle.is_file()); + + let output = util::deno_cmd() + .current_dir(util::root_path()) + .arg("run") + .arg("--reload") + .arg(&bundle) + .output() + .expect("failed to spawn script"); + // check the output of the the bundle program. + assert!(std::str::from_utf8(&output.stdout) + .unwrap() + .trim() + .ends_with("{\"foo\":{\"bar\":true,\"baz\":[\"qat\",1]}}")); + assert_eq!(output.stderr, b""); +} + #[test] fn bundle_tla() { // First we have to generate a bundle of some module that has exports.