Skip to content

Commit

Permalink
refactor(cli): rewrite Deno.transpileOnly() to use SWC (#8090)
Browse files Browse the repository at this point in the history
Co-authored-by: Kitson Kelly <me@kitsonkelly.com>
  • Loading branch information
bartlomieju and kitsonk committed Oct 26, 2020
1 parent aebbdd5 commit 57cad53
Show file tree
Hide file tree
Showing 8 changed files with 178 additions and 131 deletions.
18 changes: 11 additions & 7 deletions cli/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ impl From<tsc_config::TsConfig> for EmitOptions {
EmitOptions {
check_js: options.check_js,
emit_metadata: options.emit_decorator_metadata,
inline_source_map: true,
inline_source_map: options.inline_source_map,
jsx_factory: options.jsx_factory,
jsx_fragment_factory: options.jsx_fragment_factory,
transform_jsx: options.jsx == "react",
Expand Down Expand Up @@ -356,8 +356,11 @@ impl ParsedModule {
/// - `source` - The source code for the module.
/// - `media_type` - The media type for the module.
///
// NOTE(bartlomieju): `specifier` has `&str` type instead of
// `&ModuleSpecifier` because runtime compiler APIs don't
// require valid module specifiers
pub fn parse(
specifier: &ModuleSpecifier,
specifier: &str,
source: &str,
media_type: &MediaType,
) -> Result<ParsedModule, AnyError> {
Expand Down Expand Up @@ -505,8 +508,9 @@ mod tests {
let source = r#"import * as bar from "./test.ts";
const foo = await import("./foo.ts");
"#;
let parsed_module = parse(&specifier, source, &MediaType::JavaScript)
.expect("could not parse module");
let parsed_module =
parse(specifier.as_str(), source, &MediaType::JavaScript)
.expect("could not parse module");
let actual = parsed_module.analyze_dependencies();
assert_eq!(
actual,
Expand Down Expand Up @@ -553,7 +557,7 @@ mod tests {
}
}
"#;
let module = parse(&specifier, source, &MediaType::TypeScript)
let module = parse(specifier.as_str(), source, &MediaType::TypeScript)
.expect("could not parse module");
let (code, maybe_map) = module
.transpile(&EmitOptions::default())
Expand All @@ -577,7 +581,7 @@ mod tests {
}
}
"#;
let module = parse(&specifier, source, &MediaType::TSX)
let module = parse(specifier.as_str(), source, &MediaType::TSX)
.expect("could not parse module");
let (code, _) = module
.transpile(&EmitOptions::default())
Expand Down Expand Up @@ -608,7 +612,7 @@ mod tests {
}
}
"#;
let module = parse(&specifier, source, &MediaType::TypeScript)
let module = parse(specifier.as_str(), source, &MediaType::TypeScript)
.expect("could not parse module");
let (code, _) = module
.transpile(&EmitOptions::default())
Expand Down
3 changes: 1 addition & 2 deletions cli/dts/lib.deno.unstable.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -480,8 +480,7 @@ declare namespace Deno {
* source map.
* @param options An option object of options to send to the compiler. This is
* a subset of ts.CompilerOptions which can be supported by Deno.
* Many of the options related to type checking and emitting
* type declaration files will have no impact on the output.
* If unsupported option is passed then the API will throw an error.
*/
export function transpileOnly(
sources: Record<string, string>,
Expand Down
11 changes: 7 additions & 4 deletions cli/module_graph2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,8 @@ impl Module {
/// Parse a module, populating the structure with data retrieved from the
/// source of the module.
pub fn parse(&mut self) -> Result<(), AnyError> {
let parsed_module = parse(&self.specifier, &self.source, &self.media_type)?;
let parsed_module =
parse(self.specifier.as_str(), &self.source, &self.media_type)?;

// parse out any triple slash references
for comment in parsed_module.get_leading_comments().iter() {
Expand Down Expand Up @@ -639,12 +640,13 @@ impl Graph2 {
let mut ts_config = TsConfig::new(json!({
"checkJs": false,
"emitDecoratorMetadata": false,
"inlineSourceMap": true,
"jsx": "react",
"jsxFactory": "React.createElement",
"jsxFragmentFactory": "React.Fragment",
}));
let maybe_ignored_options =
ts_config.merge_user_config(options.maybe_config_path)?;
ts_config.merge_tsconfig(options.maybe_config_path)?;
let emit_options: EmitOptions = ts_config.into();
let cm = Rc::new(swc_common::SourceMap::new(
swc_common::FilePathMapping::empty(),
Expand Down Expand Up @@ -730,7 +732,7 @@ impl Graph2 {
}));
}
let maybe_ignored_options =
config.merge_user_config(options.maybe_config_path)?;
config.merge_tsconfig(options.maybe_config_path)?;

// Short circuit if none of the modules require an emit, or all of the
// modules that require an emit have a valid emit. There is also an edge
Expand Down Expand Up @@ -1187,13 +1189,14 @@ impl Graph2 {
let mut ts_config = TsConfig::new(json!({
"checkJs": false,
"emitDecoratorMetadata": false,
"inlineSourceMap": true,
"jsx": "react",
"jsxFactory": "React.createElement",
"jsxFragmentFactory": "React.Fragment",
}));

let maybe_ignored_options =
ts_config.merge_user_config(options.maybe_config_path)?;
ts_config.merge_tsconfig(options.maybe_config_path)?;

let emit_options: EmitOptions = ts_config.clone().into();

Expand Down
59 changes: 53 additions & 6 deletions cli/ops/runtime_compiler.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.

use crate::ast;
use crate::colors;
use crate::media_type::MediaType;
use crate::permissions::Permissions;
use crate::tsc::runtime_bundle;
use crate::tsc::runtime_compile;
use crate::tsc::runtime_transpile;
use crate::tsc_config;
use deno_core::error::AnyError;
use deno_core::futures::FutureExt;
use deno_core::serde::Serialize;
use deno_core::serde_json;
use deno_core::serde_json::json;
use deno_core::serde_json::Value;
use deno_core::BufVec;
use deno_core::OpState;
Expand Down Expand Up @@ -71,16 +76,58 @@ struct TranspileArgs {
options: Option<String>,
}

#[derive(Debug, Serialize)]
struct RuntimeTranspileEmit {
source: String,
map: Option<String>,
}

async fn op_transpile(
state: Rc<RefCell<OpState>>,
args: Value,
_data: BufVec,
) -> Result<Value, AnyError> {
super::check_unstable2(&state, "Deno.transpile");
super::check_unstable2(&state, "Deno.transpileOnly");
let args: TranspileArgs = serde_json::from_value(args)?;
let cli_state = super::global_state2(&state);
let program_state = cli_state.clone();
let result =
runtime_transpile(program_state, &args.sources, &args.options).await?;

let mut compiler_options = tsc_config::TsConfig::new(json!({
"checkJs": true,
"emitDecoratorMetadata": false,
"jsx": "react",
"jsxFactory": "React.createElement",
"jsxFragmentFactory": "React.Fragment",
"inlineSourceMap": false,
}));

let user_options: HashMap<String, Value> = if let Some(options) = args.options
{
serde_json::from_str(&options)?
} else {
HashMap::new()
};
let maybe_ignored_options =
compiler_options.merge_user_config(&user_options)?;
// TODO(@kitsonk) these really should just be passed back to the caller
if let Some(ignored_options) = maybe_ignored_options {
info!("{}: {}", colors::yellow("warning"), ignored_options);
}

let emit_options: ast::EmitOptions = compiler_options.into();
let mut emit_map = HashMap::new();

for (specifier, source) in args.sources {
let media_type = MediaType::from(&specifier);
let parsed_module = ast::parse(&specifier, &source, &media_type)?;
let (source, maybe_source_map) = parsed_module.transpile(&emit_options)?;

emit_map.insert(
specifier.to_string(),
RuntimeTranspileEmit {
source,
map: maybe_source_map,
},
);
}
let result = serde_json::to_value(emit_map)?;
Ok(result)
}
9 changes: 4 additions & 5 deletions cli/tests/compiler_api_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,17 +129,16 @@ Deno.test({
async fn() {
const actual = await Deno.transpileOnly(
{
"foo.ts": `export enum Foo { Foo, Bar, Baz };\n`,
"foo.ts": `/** This is JSDoc */\nexport enum Foo { Foo, Bar, Baz };\n`,
},
{
sourceMap: false,
module: "amd",
removeComments: true,
},
);
assert(actual);
assertEquals(Object.keys(actual), ["foo.ts"]);
assert(actual["foo.ts"].source.startsWith("define("));
assert(actual["foo.ts"].map == null);
assert(!actual["foo.ts"].source.includes("This is JSDoc"));
assert(actual["foo.ts"].map);
},
});

Expand Down
39 changes: 1 addition & 38 deletions cli/tsc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -725,41 +725,6 @@ pub async fn runtime_bundle(
Ok(serde_json::from_str::<Value>(&json_str).unwrap())
}

/// This function is used by `Deno.transpileOnly()` API.
pub async fn runtime_transpile(
program_state: Arc<ProgramState>,
sources: &HashMap<String, String>,
maybe_options: &Option<String>,
) -> Result<Value, AnyError> {
let user_options = if let Some(options) = maybe_options {
tsc_config::parse_raw_config(options)?
} else {
json!({})
};

let mut compiler_options = json!({
"esModuleInterop": true,
"module": "esnext",
"sourceMap": true,
"scriptComments": true,
"target": "esnext",
});
tsc_config::json_merge(&mut compiler_options, &user_options);

let req_msg = json!({
"type": CompilerRequestType::RuntimeTranspile,
"sources": sources,
"compilerOptions": compiler_options,
})
.to_string();

let json_str =
execute_in_tsc(program_state, req_msg).map_err(extract_js_error)?;
let v = serde_json::from_str::<Value>(&json_str)
.expect("Error decoding JSON string.");
Ok(v)
}

#[derive(Clone, Debug, PartialEq)]
pub struct ImportDesc {
pub specifier: String,
Expand Down Expand Up @@ -793,7 +758,7 @@ pub fn pre_process_file(
analyze_dynamic_imports: bool,
) -> Result<(Vec<ImportDesc>, Vec<TsReferenceDesc>), AnyError> {
let specifier = ModuleSpecifier::resolve_url_or_path(file_name)?;
let module = parse(&specifier, source_code, &media_type)?;
let module = parse(specifier.as_str(), source_code, &media_type)?;

let dependency_descriptors = module.analyze_dependencies();

Expand Down Expand Up @@ -894,7 +859,6 @@ fn parse_deno_types(comment: &str) -> Option<String> {
pub enum CompilerRequestType {
RuntimeCompile = 2,
RuntimeBundle = 3,
RuntimeTranspile = 4,
}

impl Serialize for CompilerRequestType {
Expand All @@ -905,7 +869,6 @@ impl Serialize for CompilerRequestType {
let value: i32 = match self {
CompilerRequestType::RuntimeCompile => 2 as i32,
CompilerRequestType::RuntimeBundle => 3 as i32,
CompilerRequestType::RuntimeTranspile => 4 as i32,
};
Serialize::serialize(&value, serializer)
}
Expand Down
31 changes: 0 additions & 31 deletions cli/tsc/99_main_compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -599,7 +599,6 @@ delete Object.prototype.__proto__;
const CompilerRequestType = {
RuntimeCompile: 2,
RuntimeBundle: 3,
RuntimeTranspile: 4,
};

function createBundleWriteFile(state) {
Expand Down Expand Up @@ -999,31 +998,6 @@ delete Object.prototype.__proto__;
};
}

function runtimeTranspile(request) {
const result = {};
const { sources, compilerOptions } = request;

const parseResult = parseCompilerOptions(
compilerOptions,
);
const options = parseResult.options;
// TODO(bartlomieju): this options is excluded by `ts.convertCompilerOptionsFromJson`
// however stuff breaks if it's not passed (type_directives_js_main.js, compiler_js_error.ts)
options.allowNonTsExtensions = true;

for (const [fileName, inputText] of Object.entries(sources)) {
const { outputText: source, sourceMapText: map } = ts.transpileModule(
inputText,
{
fileName,
compilerOptions: options,
},
);
result[fileName] = { source, map };
}
return result;
}

function opCompilerRespond(msg) {
core.jsonOpSync("op_compiler_respond", msg);
}
Expand All @@ -1041,11 +1015,6 @@ delete Object.prototype.__proto__;
opCompilerRespond(result);
break;
}
case CompilerRequestType.RuntimeTranspile: {
const result = runtimeTranspile(request);
opCompilerRespond(result);
break;
}
default:
throw new Error(
`!!! unhandled CompilerRequestType: ${request.type} (${
Expand Down
Loading

0 comments on commit 57cad53

Please sign in to comment.