diff --git a/cli/js/compiler/host.ts b/cli/js/compiler/host.ts index 627c52970292caca5590e8547bdfdbc83884721c..457388bd9a4a6ba815c40e75ce15e6d21256762e 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 159ccda857c1fbe3054879c3d17070c3d6c39460..e400acbf568c1fa06526e49785ca19620c2e7de9 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 09725fc223f4272d6f22e3c5883895993f7eca1b..8acc83a2d94ca5c88a45e2fdb9043614eb4d3b3c 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 e6ed364dab119ea121c0af0e51749f85ac4931cc..baa7b4c1c1c7b0294dd5ce20f68d48a931daa06a 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 9cdf0af563ae9ac308236a5e73fdd46540344051..29203d411b3de66a475c8bc01cbffb1fb1157356 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.