Conversation
Add a new `deno transpile` subcommand that transpiles TypeScript/JSX/TSX files to JavaScript using deno_ast. Supports single and multiple file transpilation with configurable source map output (none/inline/separate). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When `--declaration` is passed, invoke TSC with `emitDeclarationOnly` to generate .d.ts type declaration files alongside the transpiled JS. Changes: - Extend TSC `op_emit` to capture .d.ts files instead of panicking - Add `emitted_files` field to TSC Response to propagate declaration output - Call `program.emit()` in the JS compiler when declaration mode is active - Build module graph and invoke TSC from the transpile tool Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
9 test cases covering: - Basic transpile to stdout - Output to file with -o flag - Multiple files with --outdir - Inline and separate source map modes - .d.ts declaration generation with --declaration - Error on -o with multiple files - Skipping non-emittable .js files - TSX transpilation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
| Ok(()) | ||
| } | ||
|
|
||
| fn transpile_parse( |
| // Resolve compiler options, adding declaration-specific settings | ||
| let compiler_options_resolver = factory.compiler_options_resolver()?; | ||
| let first_specifier = &root_names[0].0; | ||
| let base_compiler_options = compiler_options_resolver | ||
| .for_specifier(first_specifier) | ||
| .compiler_options_for_lib(cli_options.ts_type_lib_window())?; | ||
|
|
||
| // Merge declaration options into the base compiler options | ||
| let mut config_value = | ||
| deno_core::serde_json::to_value(base_compiler_options.as_ref())?; | ||
| let config_obj = config_value | ||
| .as_object_mut() | ||
| .ok_or_else(|| anyhow::anyhow!("Invalid compiler options"))?; | ||
| config_obj.insert("declaration".into(), json!(true)); | ||
| config_obj.insert("emitDeclarationOnly".into(), json!(true)); | ||
| config_obj.insert("noEmit".into(), json!(false)); | ||
|
|
||
| let compiler_options = Arc::new(CompilerOptions::new(config_value)); | ||
|
|
||
| let hash_data = | ||
| deno_lib::util::hash::FastInsecureHasher::new_deno_versioned() | ||
| .write_hashable(&compiler_options) | ||
| .finish(); | ||
|
|
||
| let jsx_import_source_config_resolver = Arc::new( | ||
| deno_resolver::deno_json::JsxImportSourceConfigResolver::from_compiler_options_resolver( | ||
| compiler_options_resolver, | ||
| )?, | ||
| ); | ||
|
|
||
| // Set up npm state if available | ||
| let maybe_npm = { | ||
| let cjs_tracker = Arc::new(tsc::TypeCheckingCjsTracker::new( | ||
| factory.cjs_tracker()?.clone(), | ||
| factory.module_info_cache()?.clone(), | ||
| )); | ||
| let node_resolver = factory.node_resolver().await?.clone(); | ||
| let npm_resolver = factory.npm_resolver().await?.clone(); | ||
| let package_json_resolver = factory.pkg_json_resolver()?.clone(); | ||
| Some(tsc::RequestNpmState { | ||
| cjs_tracker, | ||
| node_resolver, | ||
| npm_resolver, | ||
| package_json_resolver, | ||
| }) | ||
| }; | ||
|
|
||
| let response = tsc::exec( | ||
| tsc::Request { | ||
| config: compiler_options, | ||
| debug: cli_options.log_level() == Some(log::Level::Debug), | ||
| graph: Arc::new(graph), | ||
| jsx_import_source_config_resolver, | ||
| hash_data, | ||
| maybe_npm, | ||
| maybe_tsbuildinfo: None, | ||
| root_names, | ||
| check_mode: TypeCheckMode::All, | ||
| initial_cwd: cwd.to_path_buf(), | ||
| }, | ||
| None, | ||
| None, | ||
| )?; |
There was a problem hiding this comment.
This seems like way too much setup code. Can't we use the TypeChecker here instead? tsc::exec( is very low level to be calling here.
There was a problem hiding this comment.
I looked into using TypeChecker here but it doesn't currently support returning emitted files — check_diagnostics calls tsc::exec internally but only exposes the diagnostics, not the emitted_files from the Response.
To make this work via TypeChecker, we'd need to add a new method (something like emit_declarations) that wraps the tsc::exec call and returns the emitted .d.ts files. Happy to do that if you think it belongs there — would keep transpile.rs much simpler and make declaration emission reusable.
In the meantime I've simplified the setup by using factory.create_request_npm_state() (new factory method) and getting all other components from the factory directly.
There was a problem hiding this comment.
I mean, we should look into adding a method and create_request_npm_state() into the tsc module. We shouldn't be exposing such low level stuff so high up or the code is going to get complex/sloppy.
There was a problem hiding this comment.
I can look into refactoring this if you'd like. I think we could move it into TypeChecker.
- Add arg parsing unit tests for transpile subcommand - Create CliFactory at top of transpile() and use it throughout - Use sys.with_paths_in_errors() for filesystem ops with better error context - Add create_request_npm_state() factory method to reduce duplication - Use deno_path_util::url_to_file_path instead of specifier.to_file_path() - Add error context to all filesystem operations and compute_output_path Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Preserve relative directory structure in --outdir instead of flattening - Fix outdir spec test to work on Windows (backslash path separators) - Remove unnecessary canonicalize_path call - Clarify --declaration behavior in help text Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Make --output and --outdir mutually exclusive via clap conflicts_with - Error when --declaration used without -o/--outdir - Make type-check errors from --declaration hard errors (exit code 1) - Map .mts->.mjs, .cts->.cjs instead of always .js - Error on --source-map separate when outputting to stdout - Gate .d.ts capture in op_emit_inner behind capture_emitted_files flag - Use BTreeMap for emitted_files for deterministic output order - Error when all input files are skipped (nothing transpiled) - Fix strip_prefix(cwd) producing bad paths for files outside cwd - Add tests for new error cases Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
deno transpilesubcommand that transpiles TypeScript/JSX/TSX files to JavaScript usingdeno_ast, with configurable source map output (--source-map none|inline|separate)--declarationflag to generate.d.tstype declaration files via the TypeScript compiler-o), multi-file (--outdir), and stdout output modesChanges
cli/args/flags.rs—TranspileFlags,SourceMapMode, clap subcommand definition and parsercli/tools/transpile.rs— New tool: transpilation viadeno_ast, declaration generation viatsc::exec()cli/tsc/js.rs/cli/tsc/mod.rs/cli/tsc/go.rs— Extendop_emitto capture.d.tsfiles, addemitted_filestoResponsecli/tsc/99_main_compiler.js— Callprogram.emit()whendeclaration/emitDeclarationOnlyis setcli/factory.rs/cli/lib.rs/cli/tools/mod.rs— WiringTest plan
tests/specs/transpile/covering:-o) and directory (--outdir).d.tsdeclaration generation-o).jsfiles./x test-spec transpile🤖 Generated with Claude Code