From 0cfdf5cedefe827cd28d9cef006fc78e1c054e98 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Mon, 30 May 2022 09:39:14 -0400 Subject: [PATCH] refactor: use deno_emit (#14737) --- Cargo.lock | 15 +++ cli/Cargo.toml | 1 + cli/emit.rs | 295 ------------------------------------------------- cli/main.rs | 19 ++-- 4 files changed, 25 insertions(+), 305 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 299749c213..e104af655c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -748,6 +748,7 @@ dependencies = [ "deno_core", "deno_crypto", "deno_doc", + "deno_emit", "deno_fetch", "deno_graph", "deno_lint", @@ -921,6 +922,20 @@ dependencies = [ "termcolor", ] +[[package]] +name = "deno_emit" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d43a724dec6898f53984acc966d4ccf24d4d4c0a568db8e4429055166e3c86d" +dependencies = [ + "anyhow", + "base64 0.13.0", + "deno_ast", + "deno_graph", + "futures", + "parking_lot 0.11.2", +] + [[package]] name = "deno_fetch" version = "0.77.0" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index a0540a4251..fc09642e99 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -48,6 +48,7 @@ winres = "=0.1.12" deno_ast = { version = "0.15.0", features = ["bundler", "cjs", "codegen", "dep_graph", "module_specifier", "proposal", "react", "sourcemap", "transforms", "transpiling", "typescript", "view", "visit"] } deno_core = { version = "0.136.0", path = "../core" } deno_doc = "0.35.0" +deno_emit = "0.2.0" deno_graph = "0.27.0" deno_lint = { version = "0.30.0", features = ["docs"] } deno_runtime = { version = "0.62.0", path = "../runtime" } diff --git a/cli/emit.rs b/cli/emit.rs index 57ed556dc4..a4fdedab79 100644 --- a/cli/emit.rs +++ b/cli/emit.rs @@ -15,28 +15,12 @@ use crate::diagnostics::Diagnostics; use crate::flags; use crate::graph_util::GraphData; use crate::graph_util::ModuleEntry; -use crate::text_encoding::strip_bom; use crate::tsc; use crate::version; -use deno_ast::get_syntax; -use deno_ast::swc; use deno_ast::swc::bundler::Hook; use deno_ast::swc::bundler::ModuleRecord; -use deno_ast::swc::common::comments::SingleThreadedComments; -use deno_ast::swc::common::FileName; -use deno_ast::swc::common::Mark; -use deno_ast::swc::common::SourceMap; use deno_ast::swc::common::Span; -use deno_ast::swc::common::Spanned; -use deno_ast::swc::parser::error::Error as SwcError; -use deno_ast::swc::parser::lexer::Lexer; -use deno_ast::swc::parser::StringInput; -use deno_ast::Diagnostic; -use deno_ast::LineAndColumnDisplay; -use deno_ast::SourceRangedForSpanned; -use deno_core::anyhow::anyhow; -use deno_core::anyhow::Context; use deno_core::error::AnyError; use deno_core::parking_lot::RwLock; use deno_core::serde::Deserialize; @@ -55,18 +39,10 @@ use deno_graph::ResolutionError; use std::collections::HashMap; use std::collections::HashSet; use std::fmt; -use std::rc::Rc; use std::result; use std::sync::Arc; use std::time::Instant; -const IGNORE_DIRECTIVES: &[&str] = &[ - "// deno-fmt-ignore-file", - "// deno-lint-ignore-file", - "// This code was bundled using `deno bundle` and it's not recommended to edit it manually", - "" -]; - /// Represents the "default" type library that should be used when type /// checking the code in the module graph. Note that a user provided config /// of `"lib"` would override this value. @@ -487,277 +463,6 @@ pub fn check_and_maybe_emit( }) } -pub enum BundleType { - /// Return the emitted contents of the program as a single "flattened" ES - /// module. - Module, - /// Return the emitted contents of the program as a single script that - /// executes the program using an immediately invoked function execution - /// (IIFE). - Classic, -} - -impl From for swc::bundler::ModuleType { - fn from(bundle_type: BundleType) -> Self { - match bundle_type { - BundleType::Classic => Self::Iife, - BundleType::Module => Self::Es, - } - } -} - -pub struct BundleOptions { - pub bundle_type: BundleType, - pub ts_config: TsConfig, - pub emit_ignore_directives: bool, -} - -/// A module loader for swc which does the appropriate retrieval and transpiling -/// of modules from the graph. -struct BundleLoader<'a> { - cm: Rc, - emit_options: &'a deno_ast::EmitOptions, - graph: &'a ModuleGraph, -} - -impl swc::bundler::Load for BundleLoader<'_> { - fn load( - &self, - file_name: &swc::common::FileName, - ) -> Result { - match file_name { - swc::common::FileName::Url(specifier) => { - if let Some(m) = self.graph.get(specifier) { - let (fm, module) = transpile_module( - specifier, - m.maybe_source.as_ref().map(|s| s as &str).unwrap_or(""), - m.media_type, - self.emit_options, - self.cm.clone(), - )?; - Ok(swc::bundler::ModuleData { - fm, - module, - helpers: Default::default(), - }) - } else { - Err(anyhow!( - "Module \"{}\" unexpectedly missing when bundling.", - specifier - )) - } - } - _ => unreachable!( - "Received a request for unsupported filename {:?}", - file_name - ), - } - } -} - -/// Transpiles a source module into an swc SourceFile. -fn transpile_module( - specifier: &ModuleSpecifier, - source: &str, - media_type: MediaType, - options: &deno_ast::EmitOptions, - cm: Rc, -) -> Result<(Rc, swc::ast::Module), AnyError> { - let source = strip_bom(source); - let source = if media_type == MediaType::Json { - format!( - "export default JSON.parse(`{}`);", - source.replace("${", "\\${").replace('`', "\\`") - ) - } else { - source.to_string() - }; - let source_file = - cm.new_source_file(FileName::Url(specifier.clone()), source); - let input = StringInput::from(&*source_file); - let comments = SingleThreadedComments::default(); - let syntax = if media_type == MediaType::Json { - get_syntax(MediaType::JavaScript) - } else { - get_syntax(media_type) - }; - let lexer = Lexer::new(syntax, deno_ast::ES_VERSION, input, Some(&comments)); - let mut parser = swc::parser::Parser::new_from(lexer); - let module = parser - .parse_module() - .map_err(|e| swc_err_to_diagnostic(&cm, specifier, e))?; - let diagnostics = parser - .take_errors() - .into_iter() - .map(|e| swc_err_to_diagnostic(&cm, specifier, e)) - .collect::>(); - - let top_level_mark = Mark::fresh(Mark::root()); - let program = deno_ast::fold_program( - swc::ast::Program::Module(module), - options, - cm, - &comments, - top_level_mark, - &diagnostics, - )?; - let module = match program { - swc::ast::Program::Module(module) => module, - _ => unreachable!(), - }; - - Ok((source_file, module)) -} - -fn swc_err_to_diagnostic( - source_map: &SourceMap, - specifier: &ModuleSpecifier, - err: SwcError, -) -> Diagnostic { - let location = source_map.lookup_char_pos(err.span().lo); - Diagnostic { - specifier: specifier.to_string(), - range: err.range(), - display_position: LineAndColumnDisplay { - line_number: location.line, - column_number: location.col_display + 1, - }, - kind: err.into_kind(), - } -} - -/// A resolver implementation for swc that resolves specifiers from the graph. -struct BundleResolver<'a>(&'a ModuleGraph); - -impl swc::bundler::Resolve for BundleResolver<'_> { - fn resolve( - &self, - referrer: &swc::common::FileName, - specifier: &str, - ) -> Result { - let referrer = if let swc::common::FileName::Url(referrer) = referrer { - referrer - } else { - unreachable!( - "An unexpected referrer was passed when bundling: {:?}", - referrer - ); - }; - if let Some(specifier) = - self.0.resolve_dependency(specifier, referrer, false) - { - Ok(deno_ast::swc::common::FileName::Url(specifier.clone())) - } else { - Err(anyhow!( - "Cannot resolve \"{}\" from \"{}\".", - specifier, - referrer - )) - } - } -} - -/// Given a module graph, generate and return a bundle of the graph and -/// optionally its source map. Unlike emitting with `check_and_maybe_emit` and -/// `emit`, which store the emitted modules in the cache, this function simply -/// returns the output. -pub fn bundle( - graph: &ModuleGraph, - options: BundleOptions, -) -> Result<(String, Option), AnyError> { - let globals = swc::common::Globals::new(); - deno_ast::swc::common::GLOBALS.set(&globals, || { - let emit_options: deno_ast::EmitOptions = options.ts_config.into(); - let source_map_config = deno_ast::SourceMapConfig { - inline_sources: emit_options.inline_sources, - }; - - let cm = Rc::new(swc::common::SourceMap::new( - swc::common::FilePathMapping::empty(), - )); - let loader = BundleLoader { - graph, - emit_options: &emit_options, - cm: cm.clone(), - }; - let resolver = BundleResolver(graph); - let config = swc::bundler::Config { - module: options.bundle_type.into(), - ..Default::default() - }; - // This hook will rewrite the `import.meta` when bundling to give a consistent - // behavior between bundled and unbundled code. - let hook = Box::new(BundleHook); - let mut bundler = swc::bundler::Bundler::new( - &globals, - cm.clone(), - loader, - resolver, - config, - hook, - ); - let mut entries = HashMap::new(); - entries.insert( - "bundle".to_string(), - swc::common::FileName::Url(graph.roots[0].0.clone()), - ); - let output = bundler - .bundle(entries) - .context("Unable to output during bundling.")?; - let mut buf = Vec::new(); - let mut srcmap = Vec::new(); - { - let cfg = swc::codegen::Config { - minify: false, - ascii_only: false, - target: deno_ast::ES_VERSION, - }; - let mut wr = Box::new(swc::codegen::text_writer::JsWriter::new( - cm.clone(), - "\n", - &mut buf, - Some(&mut srcmap), - )); - - if options.emit_ignore_directives { - // write leading comments in bundled file - use swc::codegen::text_writer::WriteJs; - let cmt = IGNORE_DIRECTIVES.join("\n") + "\n"; - wr.write_comment(&cmt)?; - } - - let mut emitter = swc::codegen::Emitter { - cfg, - cm: cm.clone(), - comments: None, - wr, - }; - emitter - .emit_module(&output[0].module) - .context("Unable to emit during bundling.")?; - } - let mut code = - String::from_utf8(buf).context("Emitted code is an invalid string.")?; - let mut maybe_map: Option = None; - { - let mut buf = Vec::new(); - cm.build_source_map_with_config(&mut srcmap, None, source_map_config) - .to_writer(&mut buf)?; - if emit_options.inline_source_map { - let encoded_map = format!( - "//# sourceMappingURL=data:application/json;base64,{}\n", - base64::encode(buf) - ); - code.push_str(&encoded_map); - } else if emit_options.source_map { - maybe_map = Some(String::from_utf8(buf)?); - } - } - - Ok((code, maybe_map)) - }) -} - pub struct EmitOptions { pub ts_config: TsConfig, pub reload: bool, diff --git a/cli/main.rs b/cli/main.rs index 5fb0c1a90a..b6c7e97ff6 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -758,7 +758,7 @@ fn bundle_module_graph( graph: &deno_graph::ModuleGraph, ps: &ProcState, flags: &Flags, -) -> Result<(String, Option), AnyError> { +) -> Result { info!("{} {}", colors::green("Bundle"), graph.roots[0].0); let (ts_config, maybe_ignored_options) = emit::get_ts_config( @@ -772,11 +772,11 @@ fn bundle_module_graph( } } - emit::bundle( + deno_emit::bundle_graph( graph, - emit::BundleOptions { - bundle_type: emit::BundleType::Module, - ts_config, + deno_emit::BundleOptions { + bundle_type: deno_emit::BundleType::Module, + emit_options: ts_config.into(), emit_ignore_directives: true, }, ) @@ -836,12 +836,11 @@ async fn bundle_command( let operation = |(ps, graph): (ProcState, Arc)| { let out_file = bundle_flags.out_file.clone(); async move { - let (bundle_emit, maybe_bundle_map) = - bundle_module_graph(graph.as_ref(), &ps, &ps.flags)?; + let bundle_output = bundle_module_graph(graph.as_ref(), &ps, &ps.flags)?; debug!(">>>>> bundle END"); if let Some(out_file) = out_file.as_ref() { - let output_bytes = bundle_emit.as_bytes(); + let output_bytes = bundle_output.code.as_bytes(); let output_len = output_bytes.len(); fs_util::write_file(out_file, output_bytes, 0o644)?; info!( @@ -850,7 +849,7 @@ async fn bundle_command( out_file, colors::gray(display::human_size(output_len as f64)) ); - if let Some(bundle_map) = maybe_bundle_map { + if let Some(bundle_map) = bundle_output.maybe_map { let map_bytes = bundle_map.as_bytes(); let map_len = map_bytes.len(); let ext = if let Some(curr_ext) = out_file.extension() { @@ -868,7 +867,7 @@ async fn bundle_command( ); } } else { - println!("{}", bundle_emit); + println!("{}", bundle_output.code); } Ok(())